Merge pull request 'fix(kube): switch to --kubeconfig flag, add Test Connection diagnostic, fix SelectValue label' (#82) from fix/kube-select-label-and-context into master
Some checks failed
Test / frontend-typecheck (push) Successful in 2m1s
Auto Tag / build-linux-amd64 (push) Successful in 10m6s
Auto Tag / build-windows-amd64 (push) Successful in 11m43s
Auto Tag / build-linux-arm64 (push) Successful in 11m41s
Test / rust-fmt-check (push) Successful in 16m8s
Auto Tag / build-macos-arm64 (push) Has been cancelled
Test / rust-clippy (push) Successful in 17m52s
Auto Tag / autotag (push) Successful in 16s
Test / rust-tests (push) Successful in 19m23s
Auto Tag / wiki-sync (push) Successful in 17s
Auto Tag / changelog (push) Successful in 1m41s
Test / frontend-tests (push) Successful in 1m50s

Reviewed-on: #82
This commit is contained in:
sarman 2026-06-08 01:50:24 +00:00
commit d65593af1e
4 changed files with 179 additions and 41 deletions

View File

@ -199,6 +199,61 @@ pub async fn connect_cluster_from_kubeconfig(
Ok(()) Ok(())
} }
/// Diagnostic: test a kubeconfig's ability to reach the cluster.
///
/// Returns a human-readable summary including the context name, kubectl binary
/// path, exit code, and the full stdout/stderr from `kubectl cluster-info`.
/// This command is safe to call at any time — it writes a temp file, tests the
/// connection, then deletes the file regardless of the outcome.
#[tauri::command]
pub async fn test_kubectl_connection(
cluster_id: String,
state: State<'_, AppState>,
) -> Result<String, String> {
let (kubeconfig_content, context) = {
let clusters = state.clusters.lock().await;
let cluster = clusters.get(&cluster_id).ok_or_else(|| {
format!(
"Cluster {} not found in session — try re-selecting the cluster",
cluster_id
)
})?;
(cluster.kubeconfig_content.clone(), cluster.context.clone())
};
let temp_dir = std::env::temp_dir();
let temp_path = temp_dir.join(format!("kubeconfig-{}-diag.yaml", cluster_id));
let _cleanup = TempFileCleanup(temp_path.clone());
std::fs::write(&temp_path, kubeconfig_content.as_ref())
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
let kubectl_path = locate_kubectl()?;
let output = Command::new(&kubectl_path)
.arg("cluster-info")
.arg("--context")
.arg(context.as_str())
.arg("--kubeconfig")
.arg(&temp_path)
.output()
.await
.map_err(|e| format!("Failed to execute kubectl: {e}"))?;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let exit_code = output.status.code().unwrap_or(-1);
Ok(format!(
"Context: {context}\nKubectl: {kubectl}\nExit: {exit}\n\n--- stdout ---\n{stdout}\n--- stderr ---\n{stderr}",
context = context,
kubectl = kubectl_path.display(),
exit = exit_code,
stdout = if stdout.is_empty() { "(none)" } else { &stdout },
stderr = if stderr.is_empty() { "(none)" } else { &stderr },
))
}
#[tauri::command] #[tauri::command]
pub async fn remove_cluster(id: String, state: State<'_, AppState>) -> Result<(), String> { pub async fn remove_cluster(id: String, state: State<'_, AppState>) -> Result<(), String> {
// Check existence in memory BEFORE touching the DB // Check existence in memory BEFORE touching the DB
@ -280,7 +335,8 @@ pub async fn test_cluster_connection(
let output = Command::new(kubectl_path) let output = Command::new(kubectl_path)
.arg("cluster-info") .arg("cluster-info")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -334,7 +390,8 @@ pub async fn discover_pods(
.arg(&namespace) .arg(&namespace)
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -473,7 +530,8 @@ pub async fn start_port_forward(
.args(&args) .args(&args)
.arg("--context") .arg("--context")
.arg(cluster.context.as_str()) .arg(cluster.context.as_str())
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.spawn() .spawn()
.map_err(|e| format!("Failed to spawn kubectl: {e}"))?; .map_err(|e| format!("Failed to spawn kubectl: {e}"))?;
@ -781,7 +839,8 @@ pub async fn list_namespaces(
.arg("namespaces") .arg("namespaces")
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -868,7 +927,8 @@ pub async fn list_pods(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -991,7 +1051,8 @@ pub async fn list_services(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -1149,7 +1210,8 @@ pub async fn list_deployments(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -1283,7 +1345,8 @@ pub async fn list_statefulsets(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -1401,7 +1464,8 @@ pub async fn list_daemonsets(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -1566,7 +1630,8 @@ pub async fn get_pod_logs(
.arg(namespace) .arg(namespace)
.arg("-c") .arg("-c")
.arg(container_name) .arg(container_name)
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -1616,7 +1681,8 @@ pub async fn scale_deployment(
.arg(replicas.to_string()) .arg(replicas.to_string())
.arg("-n") .arg("-n")
.arg(namespace) .arg(namespace)
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -1662,7 +1728,8 @@ pub async fn restart_deployment(
.arg(deployment_name) .arg(deployment_name)
.arg("-n") .arg("-n")
.arg(namespace) .arg(namespace)
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -1708,7 +1775,8 @@ pub async fn delete_resource(
.arg(resource_name) .arg(resource_name)
.arg("-n") .arg("-n")
.arg(namespace) .arg(namespace)
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -1777,7 +1845,8 @@ pub async fn exec_pod(
cmd.arg("--").arg(shell_cmd).arg("-c").arg(&command); cmd.arg("--").arg(shell_cmd).arg("-c").arg(&command);
cmd.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) cmd.arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()); .arg(context.as_str());
@ -2038,7 +2107,8 @@ pub async fn list_replicasets(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -2156,7 +2226,8 @@ pub async fn list_jobs(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -2312,7 +2383,8 @@ pub async fn list_cronjobs(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -2439,7 +2511,8 @@ pub async fn list_configmaps(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -2537,7 +2610,8 @@ pub async fn list_secrets(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -2636,7 +2710,8 @@ pub async fn list_nodes(
.arg("nodes") .arg("nodes")
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -2829,7 +2904,8 @@ pub async fn list_events(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -2958,7 +3034,8 @@ pub async fn list_ingresses(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -3084,7 +3161,8 @@ pub async fn list_persistentvolumeclaims(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -3209,7 +3287,8 @@ pub async fn list_persistentvolumes(
.arg("persistentvolumes") .arg("persistentvolumes")
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -3340,7 +3419,8 @@ pub async fn list_serviceaccounts(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -3438,7 +3518,8 @@ pub async fn list_roles(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -3523,7 +3604,8 @@ pub async fn list_clusterroles(
.arg("clusterroles") .arg("clusterroles")
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -3603,7 +3685,8 @@ pub async fn list_rolebindings(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -3699,7 +3782,8 @@ pub async fn list_clusterrolebindings(
.arg("clusterrolebindings") .arg("clusterrolebindings")
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -3790,7 +3874,8 @@ pub async fn list_horizontalpodautoscalers(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -3903,7 +3988,8 @@ pub async fn list_storageclasses(
.arg("storageclasses") .arg("storageclasses")
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -4013,7 +4099,8 @@ pub async fn list_networkpolicies(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -4124,7 +4211,8 @@ pub async fn list_resourcequotas(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -4245,7 +4333,8 @@ pub async fn list_limitranges(
let output = kubectl_cmd let output = kubectl_cmd
.arg("-o") .arg("-o")
.arg("json") .arg("json")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -4337,7 +4426,8 @@ pub async fn cordon_node(
let output = Command::new(kubectl_path) let output = Command::new(kubectl_path)
.arg("cordon") .arg("cordon")
.arg(node_name) .arg(node_name)
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -4378,7 +4468,8 @@ pub async fn uncordon_node(
let output = Command::new(kubectl_path) let output = Command::new(kubectl_path)
.arg("uncordon") .arg("uncordon")
.arg(node_name) .arg(node_name)
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -4422,7 +4513,8 @@ pub async fn drain_node(
.arg("--ignore-daemonsets") .arg("--ignore-daemonsets")
.arg("--delete-emptydir-data") .arg("--delete-emptydir-data")
.arg("--force") .arg("--force")
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -4468,7 +4560,8 @@ pub async fn rollback_deployment(
.arg(deployment_name) .arg(deployment_name)
.arg("-n") .arg("-n")
.arg(namespace) .arg(namespace)
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.output() .output()
@ -4514,7 +4607,8 @@ pub async fn create_resource(
.arg("-") .arg("-")
.arg("-n") .arg("-n")
.arg(namespace) .arg(namespace)
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.stdin(Stdio::piped()) .stdin(Stdio::piped())
@ -4577,7 +4671,8 @@ pub async fn edit_resource(
.arg("-") .arg("-")
.arg("-n") .arg("-n")
.arg(namespace) .arg(namespace)
.env("KUBECONFIG", temp_path.to_string_lossy().to_string()) .arg("--kubeconfig")
.arg(&temp_path)
.arg("--context") .arg("--context")
.arg(context.as_str()) .arg(context.as_str())
.stdin(Stdio::piped()) .stdin(Stdio::piped())

View File

@ -181,6 +181,7 @@ pub fn run() {
// Kubernetes Management // Kubernetes Management
commands::kube::add_cluster, commands::kube::add_cluster,
commands::kube::connect_cluster_from_kubeconfig, commands::kube::connect_cluster_from_kubeconfig,
commands::kube::test_kubectl_connection,
commands::kube::remove_cluster, commands::kube::remove_cluster,
commands::kube::list_clusters, commands::kube::list_clusters,
commands::kube::start_port_forward, commands::kube::start_port_forward,

View File

@ -917,6 +917,10 @@ export const removeClusterCmd = (id: string) =>
export const connectClusterFromKubeconfigCmd = (id: string) => export const connectClusterFromKubeconfigCmd = (id: string) =>
invoke<void>("connect_cluster_from_kubeconfig", { id }); invoke<void>("connect_cluster_from_kubeconfig", { id });
/** Diagnostic: runs kubectl cluster-info and returns a human-readable summary. */
export const testKubectlConnectionCmd = (clusterId: string) =>
invoke<string>("test_kubectl_connection", { clusterId });
export const listClustersCmd = () => export const listClustersCmd = () =>
invoke<ClusterInfo[]>("list_clusters"); invoke<ClusterInfo[]>("list_clusters");

View File

@ -1,11 +1,13 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Upload, Check, Trash2, FileCode } from 'lucide-react'; import { Upload, Check, Trash2, FileCode, FlaskConical } from 'lucide-react';
import { Button, Card, CardHeader, CardTitle, CardContent, Badge } from '@/components/ui'; import { Button, Card, CardHeader, CardTitle, CardContent, Badge } from '@/components/ui';
import { import {
uploadKubeconfigCmd, uploadKubeconfigCmd,
listKubeconfigsCmd, listKubeconfigsCmd,
activateKubeconfigCmd, activateKubeconfigCmd,
deleteKubeconfigCmd, deleteKubeconfigCmd,
connectClusterFromKubeconfigCmd,
testKubectlConnectionCmd,
type KubeconfigInfo, type KubeconfigInfo,
} from '@/lib/tauriCommands'; } from '@/lib/tauriCommands';
@ -15,6 +17,8 @@ export default function KubeconfigManager() {
const [uploadContent, setUploadContent] = useState(''); const [uploadContent, setUploadContent] = useState('');
const [uploadName, setUploadName] = useState(''); const [uploadName, setUploadName] = useState('');
const [error, setError] = useState(''); const [error, setError] = useState('');
const [testResult, setTestResult] = useState<{ id: string; output: string } | null>(null);
const [testingId, setTestingId] = useState<string | null>(null);
const loadConfigs = async () => { const loadConfigs = async () => {
try { try {
@ -90,6 +94,22 @@ export default function KubeconfigManager() {
} }
}; };
const handleTestConnection = async (id: string) => {
setTestingId(id);
setTestResult(null);
setError('');
try {
// Ensure the cluster is loaded into the session first
await connectClusterFromKubeconfigCmd(id).catch(() => {});
const output = await testKubectlConnectionCmd(id);
setTestResult({ id, output });
} catch (err) {
setTestResult({ id, output: String(err) });
} finally {
setTestingId(null);
}
};
return ( return (
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
<div> <div>
@ -200,6 +220,16 @@ export default function KubeconfigManager() {
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleTestConnection(config.id)}
disabled={testingId === config.id}
title="Test kubectl connection"
>
<FlaskConical className="h-4 w-4 mr-1" />
{testingId === config.id ? 'Testing…' : 'Test'}
</Button>
{!config.is_active && ( {!config.is_active && (
<Button <Button
variant="outline" variant="outline"
@ -221,6 +251,13 @@ export default function KubeconfigManager() {
</Button> </Button>
</div> </div>
</div> </div>
{/* Test result for this config */}
{testResult?.id === config.id && (
<div className="mt-3 rounded-md bg-slate-950 p-3 font-mono text-xs text-slate-300 overflow-x-auto max-h-64 overflow-y-auto">
<pre>{testResult.output}</pre>
</div>
)}
</div> </div>
))} ))}
</div> </div>
@ -243,6 +280,7 @@ export default function KubeconfigManager() {
<li>Multiple clusters can be configured and switched between</li> <li>Multiple clusters can be configured and switched between</li>
<li>The active configuration is used for kubectl commands</li> <li>The active configuration is used for kubectl commands</li>
<li>All kubeconfig files are encrypted using AES-256-GCM</li> <li>All kubeconfig files are encrypted using AES-256-GCM</li>
<li>Use the <strong>Test</strong> button to diagnose connection issues</li>
</ul> </ul>
</CardContent> </CardContent>
</Card> </Card>