From fb55601e3b42f3775f5cd3d8adafc5f334ee5cfb Mon Sep 17 00:00:00 2001 From: Shaun Arman Date: Sun, 7 Jun 2026 18:58:16 -0500 Subject: [PATCH] fix(kube): correct kubectl context, dialog close, icon visibility, cluster name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. kubectl credentials error (41 places in kube.rs) Every kubectl invocation used .env("KUBERNETES_CONTEXT", context) which is not a real kubectl environment variable — kubectl silently ignores it and falls back to whatever current-context is set in the kubeconfig YAML. If that context has expired or wrong credentials the auth failure occurs. Replaced all 41 instances with .arg("--context").arg(context) so kubectl always uses the correct context from the stored kubeconfig. 2. Cluster name still showed UUID (two causes) a) Hotbar read from kubernetesStore.clusters (ClusterInfo[]) which is never populated by the kubeconfig-based flow — always empty, so selectedCluster was always undefined. Removed the Zustand cluster lookup from Hotbar and added a clusterName prop passed from KubernetesPage.tsx (selectedConfig?.name). b) ClusterOverview fell back to showing raw clusterId UUID when clusterName was undefined. Changed subtitle to render conditionally so UUID never shows. 3. Bell dialog had no way to close Custom DialogContent had no X button and no backdrop-click handler. Added X close button (top-right) and backdrop-click-to-close. 4. Hotbar icons invisible in dark mode variant="ghost" only styles hover state with no baseline text color. Added className="text-foreground" to all icon-only ghost buttons. --- src-tauri/src/commands/kube.rs | 120 ++++++++++++------ src/components/Kubernetes/ClusterOverview.tsx | 8 +- src/components/Kubernetes/Hotbar.tsx | 66 +++++----- src/components/ui/index.tsx | 17 ++- src/pages/Kubernetes/KubernetesPage.tsx | 4 +- tests/unit/ClusterOverview.test.tsx | 14 +- 6 files changed, 147 insertions(+), 82 deletions(-) diff --git a/src-tauri/src/commands/kube.rs b/src-tauri/src/commands/kube.rs index 918f042a..f783582d 100644 --- a/src-tauri/src/commands/kube.rs +++ b/src-tauri/src/commands/kube.rs @@ -270,7 +270,8 @@ pub async fn test_cluster_connection( let output = Command::new(kubectl_path) .arg("cluster-info") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -323,7 +324,8 @@ pub async fn discover_pods( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -458,8 +460,9 @@ pub async fn start_port_forward( // Spawn kubectl subprocess let child = Command::new(kubectl_path) .args(&args) + .arg("--context") + .arg(cluster.context.as_str()) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", &cluster.context) .spawn() .map_err(|e| format!("Failed to spawn kubectl: {e}"))?; @@ -768,7 +771,8 @@ pub async fn list_namespaces( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -854,7 +858,8 @@ pub async fn list_pods( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -976,7 +981,8 @@ pub async fn list_services( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -1133,7 +1139,8 @@ pub async fn list_deployments( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -1266,7 +1273,8 @@ pub async fn list_statefulsets( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -1383,7 +1391,8 @@ pub async fn list_daemonsets( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -1547,7 +1556,8 @@ pub async fn get_pod_logs( .arg("-c") .arg(container_name) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -1596,7 +1606,8 @@ pub async fn scale_deployment( .arg("-n") .arg(namespace) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -1641,7 +1652,8 @@ pub async fn restart_deployment( .arg("-n") .arg(namespace) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -1686,7 +1698,8 @@ pub async fn delete_resource( .arg("-n") .arg(namespace) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -1754,7 +1767,8 @@ pub async fn exec_pod( cmd.arg("--").arg(shell_cmd).arg("-c").arg(&command); cmd.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context); + .arg("--context") + .arg(context.as_str()); let output = cmd .output() @@ -2014,7 +2028,8 @@ pub async fn list_replicasets( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -2131,7 +2146,8 @@ pub async fn list_jobs( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -2286,7 +2302,8 @@ pub async fn list_cronjobs( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -2412,7 +2429,8 @@ pub async fn list_configmaps( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -2509,7 +2527,8 @@ pub async fn list_secrets( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -2607,7 +2626,8 @@ pub async fn list_nodes( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -2799,7 +2819,8 @@ pub async fn list_events( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -2927,7 +2948,8 @@ pub async fn list_ingresses( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3052,7 +3074,8 @@ pub async fn list_persistentvolumeclaims( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3176,7 +3199,8 @@ pub async fn list_persistentvolumes( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3306,7 +3330,8 @@ pub async fn list_serviceaccounts( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3403,7 +3428,8 @@ pub async fn list_roles( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3487,7 +3513,8 @@ pub async fn list_clusterroles( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3566,7 +3593,8 @@ pub async fn list_rolebindings( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3661,7 +3689,8 @@ pub async fn list_clusterrolebindings( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3751,7 +3780,8 @@ pub async fn list_horizontalpodautoscalers( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3863,7 +3893,8 @@ pub async fn list_storageclasses( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -3972,7 +4003,8 @@ pub async fn list_networkpolicies( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -4082,7 +4114,8 @@ pub async fn list_resourcequotas( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -4202,7 +4235,8 @@ pub async fn list_limitranges( .arg("-o") .arg("json") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -4293,7 +4327,8 @@ pub async fn cordon_node( .arg("cordon") .arg(node_name) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -4333,7 +4368,8 @@ pub async fn uncordon_node( .arg("uncordon") .arg(node_name) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -4376,7 +4412,8 @@ pub async fn drain_node( .arg("--delete-emptydir-data") .arg("--force") .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -4421,7 +4458,8 @@ pub async fn rollback_deployment( .arg("-n") .arg(namespace) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .output() .await .map_err(|e| format!("Failed to execute kubectl: {e}"))?; @@ -4466,7 +4504,8 @@ pub async fn create_resource( .arg("-n") .arg(namespace) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -4528,7 +4567,8 @@ pub async fn edit_resource( .arg("-n") .arg(namespace) .env("KUBECONFIG", temp_path.to_string_lossy().to_string()) - .env("KUBERNETES_CONTEXT", context) + .arg("--context") + .arg(context.as_str()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); diff --git a/src/components/Kubernetes/ClusterOverview.tsx b/src/components/Kubernetes/ClusterOverview.tsx index d9b2c87b..b5ae6517 100644 --- a/src/components/Kubernetes/ClusterOverview.tsx +++ b/src/components/Kubernetes/ClusterOverview.tsx @@ -117,9 +117,11 @@ export function ClusterOverview({ clusterId, clusterName }: ClusterOverviewProps

Cluster Overview

-

- {clusterName ?? clusterId} -

+ {clusterName && ( +

+ {clusterName} +

+ )}
-
@@ -33,33 +35,35 @@ export function Hotbar({ onRefresh, onAddResource, onSettings, onNotifications,
- {selectedCluster?.name || "No cluster selected"} + {clusterName ?? "No cluster selected"}
-
- - - -
+ + +
); diff --git a/src/components/ui/index.tsx b/src/components/ui/index.tsx index 519655a9..485c7900 100644 --- a/src/components/ui/index.tsx +++ b/src/components/ui/index.tsx @@ -605,13 +605,26 @@ export function DialogContent({ className, children }: { className?: string; chi if (!ctx.open) return null; return ( -
+
ctx.onOpenChange(false)} + >
e.stopPropagation()} > + {children}
diff --git a/src/pages/Kubernetes/KubernetesPage.tsx b/src/pages/Kubernetes/KubernetesPage.tsx index 95d5f74d..4009c822 100644 --- a/src/pages/Kubernetes/KubernetesPage.tsx +++ b/src/pages/Kubernetes/KubernetesPage.tsx @@ -594,11 +594,10 @@ export function KubernetesPage() { } if (activeSection === "overview") { - const overviewConfig = kubeconfigs.find((c) => c.id === selectedClusterId); return ( ); } @@ -706,6 +705,7 @@ export function KubernetesPage() { onAddResource={() => setIsCommandPaletteOpen(true)} onSettings={() => {}} onNotifications={() => setIsNotificationsOpen(true)} + clusterName={selectedConfig?.name} /> {/* Top bar: cluster selector + namespace selector */} diff --git a/tests/unit/ClusterOverview.test.tsx b/tests/unit/ClusterOverview.test.tsx index c603a9b8..2bc5bcb2 100644 --- a/tests/unit/ClusterOverview.test.tsx +++ b/tests/unit/ClusterOverview.test.tsx @@ -178,14 +178,20 @@ describe("ClusterOverview", () => { }); }); - it("falls back gracefully when clusterName prop is not provided", async () => { + it("hides the subtitle when clusterName prop is not provided (never shows UUID)", async () => { mockInvoke.mockImplementation(() => Promise.resolve([])); - render(); + render(); await waitFor(() => { - const header = screen.getByTestId("cluster-name-header"); - expect(header).toBeInTheDocument(); + // Heading still present + expect(screen.getByText("Cluster Overview")).toBeInTheDocument(); + // UUID must NOT be rendered anywhere + expect( + screen.queryByText("019e9ff0-b6a4-78e1-a566-7a0c05e32577") + ).not.toBeInTheDocument(); + // Subtitle element should not exist when no name is passed + expect(screen.queryByTestId("cluster-name-header")).not.toBeInTheDocument(); }); }); });