feat: implement full Lens-like Kubernetes UI with resource discovery and management #75
@ -6,8 +6,10 @@ use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
use tauri::State;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::process::Command;
|
||||
use tracing::info;
|
||||
|
||||
@ -3609,44 +3611,257 @@ fn parse_hpas_json(json_str: &str) -> Result<Vec<HorizontalPodAutoscalerInfo>, S
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn cordon_node(cluster_id: String, node_name: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||
// Implementation similar to other management commands
|
||||
let clusters = state.clusters.lock().await;
|
||||
let cluster = clusters
|
||||
.get(&cluster_id)
|
||||
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||
|
||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||
let context = &cluster.context;
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-cordon.yaml", cluster_id));
|
||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||
|
||||
std::fs::write(&temp_path, kubeconfig_content)
|
||||
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
||||
|
||||
let kubectl_path = locate_kubectl()?;
|
||||
|
||||
let output = Command::new(kubectl_path)
|
||||
.arg("cordon")
|
||||
.arg(node_name)
|
||||
.env("KUBECONFIG", temp_path.to_string_lossy().to_string())
|
||||
.env("KUBERNETES_CONTEXT", context)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to execute kubectl: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(stderr.to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn uncordon_node(cluster_id: String, node_name: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||
// Implementation similar to other management commands
|
||||
let clusters = state.clusters.lock().await;
|
||||
let cluster = clusters
|
||||
.get(&cluster_id)
|
||||
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||
|
||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||
let context = &cluster.context;
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-uncordon.yaml", cluster_id));
|
||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||
|
||||
std::fs::write(&temp_path, kubeconfig_content)
|
||||
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
||||
|
||||
let kubectl_path = locate_kubectl()?;
|
||||
|
||||
let output = Command::new(kubectl_path)
|
||||
.arg("uncordon")
|
||||
.arg(node_name)
|
||||
.env("KUBECONFIG", temp_path.to_string_lossy().to_string())
|
||||
.env("KUBERNETES_CONTEXT", context)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to execute kubectl: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(stderr.to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn drain_node(cluster_id: String, node_name: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||
// Implementation similar to other management commands
|
||||
let clusters = state.clusters.lock().await;
|
||||
let cluster = clusters
|
||||
.get(&cluster_id)
|
||||
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||
|
||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||
let context = &cluster.context;
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-drain.yaml", cluster_id));
|
||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||
|
||||
std::fs::write(&temp_path, kubeconfig_content)
|
||||
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
||||
|
||||
let kubectl_path = locate_kubectl()?;
|
||||
|
||||
let output = Command::new(kubectl_path)
|
||||
.arg("drain")
|
||||
.arg(node_name)
|
||||
.arg("--ignore-daemonsets")
|
||||
.arg("--delete-emptydir-data")
|
||||
.arg("--force")
|
||||
.env("KUBECONFIG", temp_path.to_string_lossy().to_string())
|
||||
.env("KUBERNETES_CONTEXT", context)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to execute kubectl: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(stderr.to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn rollback_deployment(cluster_id: String, namespace: String, deployment_name: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||
// Implementation similar to other management commands
|
||||
let clusters = state.clusters.lock().await;
|
||||
let cluster = clusters
|
||||
.get(&cluster_id)
|
||||
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||
|
||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||
let context = &cluster.context;
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-rollback.yaml", cluster_id));
|
||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||
|
||||
std::fs::write(&temp_path, kubeconfig_content)
|
||||
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
||||
|
||||
let kubectl_path = locate_kubectl()?;
|
||||
|
||||
let output = Command::new(kubectl_path)
|
||||
.arg("rollout")
|
||||
.arg("undo")
|
||||
.arg("deployment")
|
||||
.arg(deployment_name)
|
||||
.arg("-n")
|
||||
.arg(namespace)
|
||||
.env("KUBECONFIG", temp_path.to_string_lossy().to_string())
|
||||
.env("KUBERNETES_CONTEXT", context)
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to execute kubectl: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(stderr.to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn create_resource(cluster_id: String, namespace: String, resource_type: String, yaml_content: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||
// Implementation similar to other management commands
|
||||
pub async fn create_resource(cluster_id: String, namespace: String, _resource_type: String, yaml_content: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||
let clusters = state.clusters.lock().await;
|
||||
let cluster = clusters
|
||||
.get(&cluster_id)
|
||||
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||
|
||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||
let context = &cluster.context;
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-create.yaml", cluster_id));
|
||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||
|
||||
std::fs::write(&temp_path, kubeconfig_content)
|
||||
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
||||
|
||||
let kubectl_path = locate_kubectl()?;
|
||||
|
||||
let mut cmd = Command::new(kubectl_path);
|
||||
cmd.arg("create")
|
||||
.arg("-f")
|
||||
.arg("-")
|
||||
.arg("-n")
|
||||
.arg(namespace)
|
||||
.env("KUBECONFIG", temp_path.to_string_lossy().to_string())
|
||||
.env("KUBERNETES_CONTEXT", context)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
let mut child = cmd.spawn()
|
||||
.map_err(|e| format!("Failed to spawn kubectl: {e}"))?;
|
||||
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
stdin.write_all(yaml_content.as_bytes())
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write yaml to stdin: {e}"))?;
|
||||
}
|
||||
|
||||
let output = child.wait_with_output()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to execute kubectl: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(stderr.to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn edit_resource(cluster_id: String, namespace: String, resource_type: String, resource_name: String, yaml_content: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||
// Implementation similar to other management commands
|
||||
pub async fn edit_resource(cluster_id: String, namespace: String, _resource_type: String, _resource_name: String, yaml_content: String, state: State<'_, AppState>) -> Result<(), String> {
|
||||
let clusters = state.clusters.lock().await;
|
||||
let cluster = clusters
|
||||
.get(&cluster_id)
|
||||
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||
|
||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||
let context = &cluster.context;
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-edit.yaml", cluster_id));
|
||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||
|
||||
std::fs::write(&temp_path, kubeconfig_content)
|
||||
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
||||
|
||||
let kubectl_path = locate_kubectl()?;
|
||||
|
||||
let mut cmd = Command::new(kubectl_path);
|
||||
cmd.arg("apply")
|
||||
.arg("-f")
|
||||
.arg("-")
|
||||
.arg("-n")
|
||||
.arg(namespace)
|
||||
.env("KUBECONFIG", temp_path.to_string_lossy().to_string())
|
||||
.env("KUBERNETES_CONTEXT", context)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped());
|
||||
|
||||
let mut child = cmd.spawn()
|
||||
.map_err(|e| format!("Failed to spawn kubectl: {e}"))?;
|
||||
|
||||
if let Some(mut stdin) = child.stdin.take() {
|
||||
stdin.write_all(yaml_content.as_bytes())
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write yaml to stdin: {e}"))?;
|
||||
}
|
||||
|
||||
let output = child.wait_with_output()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to execute kubectl: {e}"))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(stderr.to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@ -947,3 +947,213 @@ export const deleteResourceCmd = (clusterId: string, resourceType: string, names
|
||||
|
||||
export const execPodCmd = (clusterId: string, namespace: string, podName: string, containerName: string, command: string, shell?: string) =>
|
||||
invoke<ExecResponse>("exec_pod", { clusterId, namespace, podName, containerName, shell, command });
|
||||
|
||||
// ─── Additional Kubernetes Resource Discovery Types ───────────────────────────
|
||||
|
||||
export interface ReplicaSetInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
replicas: number;
|
||||
ready: string;
|
||||
age: string;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface JobInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
completions: string;
|
||||
duration: string;
|
||||
age: string;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface CronJobInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
schedule: string;
|
||||
active: number;
|
||||
last_schedule: string;
|
||||
age: string;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ConfigMapInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
data_keys: number;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface SecretInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
type: string;
|
||||
data_keys: number;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface NodeInfo {
|
||||
name: string;
|
||||
status: string;
|
||||
roles: string;
|
||||
version: string;
|
||||
internal_ip: string;
|
||||
external_ip?: string;
|
||||
os_image: string;
|
||||
kernel_version: string;
|
||||
kubelet_version: string;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface EventInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
event_type: string;
|
||||
reason: string;
|
||||
object: string;
|
||||
count: number;
|
||||
first_seen: string;
|
||||
last_seen: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface IngressInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
class?: string;
|
||||
host: string;
|
||||
addresses: string[];
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface PersistentVolumeClaimInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
status: string;
|
||||
volume: string;
|
||||
capacity: string;
|
||||
access_modes: string[];
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface PersistentVolumeInfo {
|
||||
name: string;
|
||||
status: string;
|
||||
capacity: string;
|
||||
access_modes: string[];
|
||||
reclaim_policy: string;
|
||||
storage_class: string;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface ServiceAccountInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
secrets: number;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface RoleInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface ClusterRoleInfo {
|
||||
name: string;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface RoleBindingInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
role: string;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface ClusterRoleBindingInfo {
|
||||
name: string;
|
||||
cluster_role: string;
|
||||
age: string;
|
||||
}
|
||||
|
||||
export interface HorizontalPodAutoscalerInfo {
|
||||
name: string;
|
||||
namespace: string;
|
||||
min_replicas: number;
|
||||
max_replicas: number;
|
||||
current_replicas: number;
|
||||
desired_replicas: number;
|
||||
age: string;
|
||||
}
|
||||
|
||||
// ─── Additional Kubernetes Resource Discovery Commands ────────────────────────
|
||||
|
||||
export const listReplicasetsCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<ReplicaSetInfo[]>("list_replicasets", { clusterId, namespace });
|
||||
|
||||
export const listJobsCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<JobInfo[]>("list_jobs", { clusterId, namespace });
|
||||
|
||||
export const listCronjobsCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<CronJobInfo[]>("list_cronjobs", { clusterId, namespace });
|
||||
|
||||
export const listConfigmapsCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<ConfigMapInfo[]>("list_configmaps", { clusterId, namespace });
|
||||
|
||||
export const listSecretsCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<SecretInfo[]>("list_secrets", { clusterId, namespace });
|
||||
|
||||
export const listNodesCmd = (clusterId: string) =>
|
||||
invoke<NodeInfo[]>("list_nodes", { clusterId });
|
||||
|
||||
export const listEventsCmd = (clusterId: string, namespace?: string) =>
|
||||
invoke<EventInfo[]>("list_events", { clusterId, namespace });
|
||||
|
||||
export const listIngressesCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<IngressInfo[]>("list_ingresses", { clusterId, namespace });
|
||||
|
||||
export const listPersistentvolumeclaimsCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<PersistentVolumeClaimInfo[]>("list_persistentvolumeclaims", { clusterId, namespace });
|
||||
|
||||
export const listPersistentvolumesCmd = (clusterId: string) =>
|
||||
invoke<PersistentVolumeInfo[]>("list_persistentvolumes", { clusterId });
|
||||
|
||||
export const listServiceaccountsCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<ServiceAccountInfo[]>("list_serviceaccounts", { clusterId, namespace });
|
||||
|
||||
export const listRolesCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<RoleInfo[]>("list_roles", { clusterId, namespace });
|
||||
|
||||
export const listClusterrolesCmd = (clusterId: string) =>
|
||||
invoke<ClusterRoleInfo[]>("list_clusterroles", { clusterId });
|
||||
|
||||
export const listRolebindingsCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<RoleBindingInfo[]>("list_rolebindings", { clusterId, namespace });
|
||||
|
||||
export const listClusterrolebindingsCmd = (clusterId: string) =>
|
||||
invoke<ClusterRoleBindingInfo[]>("list_clusterrolebindings", { clusterId });
|
||||
|
||||
export const listHorizontalpodautoscalersCmd = (clusterId: string, namespace: string) =>
|
||||
invoke<HorizontalPodAutoscalerInfo[]>("list_horizontalpodautoscalers", { clusterId, namespace });
|
||||
|
||||
// ─── Additional Kubernetes Resource Management Commands ───────────────────────
|
||||
|
||||
export const cordonNodeCmd = (clusterId: string, nodeName: string) =>
|
||||
invoke<void>("cordon_node", { clusterId, nodeName });
|
||||
|
||||
export const uncordonNodeCmd = (clusterId: string, nodeName: string) =>
|
||||
invoke<void>("uncordon_node", { clusterId, nodeName });
|
||||
|
||||
export const drainNodeCmd = (clusterId: string, nodeName: string) =>
|
||||
invoke<void>("drain_node", { clusterId, nodeName });
|
||||
|
||||
export const rollbackDeploymentCmd = (clusterId: string, namespace: string, deploymentName: string) =>
|
||||
invoke<void>("rollback_deployment", { clusterId, namespace, deploymentName });
|
||||
|
||||
export const createResourceCmd = (clusterId: string, namespace: string, resourceType: string, yamlContent: string) =>
|
||||
invoke<void>("create_resource", { clusterId, namespace, resourceType, yamlContent });
|
||||
|
||||
export const editResourceCmd = (clusterId: string, namespace: string, resourceType: string, resourceName: string, yamlContent: string) =>
|
||||
invoke<void>("edit_resource", { clusterId, namespace, resourceType, resourceName, yamlContent });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user