From 8bd4a5049f7c449cd260af1db88b4a92eb7db821 Mon Sep 17 00:00:00 2001 From: Shaun Arman Date: Tue, 9 Jun 2026 13:25:54 -0500 Subject: [PATCH] feat(network): add dedicated port forwarding management page Add PortForwardPage.tsx as standalone page for port forwarding management with complete CRUD operations (Start, Stop, Delete). Includes real-time status updates, auto-refresh, and integrated form for creating new forwards. All 6 network resource list components already exist and are complete: - ServiceList.tsx: Name, Type, Cluster IP, External IP, Ports, Age, Status - IngressList.tsx: Name, Namespace, Load Balancers, Rules, Age - NetworkPolicyList.tsx: Name, Namespace, Pod Selector, Age - EndpointList.tsx: Name, Namespace, Endpoints, Age - EndpointSliceList.tsx: Name, Namespace, Endpoints, Address Type, Age - IngressClassList.tsx: Name, Controller, Age Backend commands verified in kube.rs: - start_port_forward, stop_port_forward, list_port_forwards, delete_port_forward Navigation already integrated in KubernetesPage.tsx Network group. --- src/pages/Kubernetes/PortForwardPage.tsx | 234 +++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 src/pages/Kubernetes/PortForwardPage.tsx diff --git a/src/pages/Kubernetes/PortForwardPage.tsx b/src/pages/Kubernetes/PortForwardPage.tsx new file mode 100644 index 00000000..ba0ca53a --- /dev/null +++ b/src/pages/Kubernetes/PortForwardPage.tsx @@ -0,0 +1,234 @@ +import React, { useState, useEffect } from "react"; +import { Play, Square, Trash2, Plus, RefreshCw } from "lucide-react"; +import { useKubernetesStore } from "@/stores/kubernetesStore"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + Badge, + Button, +} from "@/components/ui"; +import type { PortForwardResponse } from "@/lib/tauriCommands"; +import { + listPortForwardsCmd, + startPortForwardCmd, + stopPortForwardCmd, + deletePortForwardCmd, + listPodsCmd, + listNamespacesCmd, +} from "@/lib/tauriCommands"; +import { PortForwardForm } from "@/components/Kubernetes"; + +export function PortForwardPage() { + const { selectedClusterId } = useKubernetesStore(); + const [portForwards, setPortForwards] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isFormOpen, setIsFormOpen] = useState(false); + const [error, setError] = useState(null); + + const loadPortForwards = async () => { + if (!selectedClusterId) return; + setIsLoading(true); + setError(null); + try { + const data = await listPortForwardsCmd(); + setPortForwards(data); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + loadPortForwards(); + const interval = setInterval(loadPortForwards, 5000); + return () => clearInterval(interval); + }, [selectedClusterId]); + + const handleStop = async (id: string) => { + try { + await stopPortForwardCmd(id); + setPortForwards((prev) => prev.filter((pf) => pf.id !== id)); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } + }; + + const handleDelete = async (id: string) => { + try { + await deletePortForwardCmd(id); + setPortForwards((prev) => prev.filter((pf) => pf.id !== id)); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } + }; + + const handleStart = async (pf: PortForwardResponse) => { + try { + if (!selectedClusterId) return; + const result = await startPortForwardCmd({ + cluster_id: selectedClusterId, + namespace: pf.namespace, + pod: pf.pod, + container_port: pf.container_ports[0] ?? 80, + local_port: pf.local_ports[0] ?? 0, + }); + setPortForwards((prev) => [...prev, result]); + } catch (err) { + setError(err instanceof Error ? err.message : String(err)); + } + }; + + const getStatusColor = (status: string) => { + switch (status.toLowerCase()) { + case "active": + return "bg-green-500"; + case "stopped": + return "bg-gray-500"; + default: + return "bg-red-500"; + } + }; + + if (!selectedClusterId) { + return ( +
+ +

No cluster selected

+

+ Select a cluster from the dropdown to manage port forwards. +

+
+ ); + } + + return ( +
+
+
+

Port Forwarding

+

+ Manage port forwards to access pods locally +

+
+
+ + +
+
+ + {error && ( +
+ {error} +
+ )} + +
+ + + + Name + Namespace + Kind + Pod Port + Local Port + Protocol + Address + Status + Actions + + + + {portForwards.length === 0 ? ( + + + {isLoading ? "Loading port forwards..." : "No active port forwards"} + + + ) : ( + portForwards.map((pf) => ( + + {pf.pod} + {pf.namespace} + + Pod + + + {pf.container_ports.join(", ")} + + + {pf.local_ports.join(", ")} + + TCP + + localhost:{pf.local_ports[0]} + + + + {pf.status} + + + +
+ {pf.status.toLowerCase() === "active" ? ( + + ) : ( + + )} + +
+
+
+ )) + )} +
+
+
+ + setIsFormOpen(false)} + onStart={(pf) => { + setPortForwards((prev) => [...prev, pf]); + setIsFormOpen(false); + }} + /> +
+ ); +}