feat(kube): Kubernetes UI — FreeLens v5 feature parity #85

Merged
sarman merged 6 commits from feat/kube-ui-feature-parity into master 2026-06-09 02:05:06 +00:00
7 changed files with 3212 additions and 1 deletions
Showing only changes of commit cd99e631a4 - Show all commits

58
scripts/download-helm.sh Normal file
View File

@ -0,0 +1,58 @@
#!/bin/bash
set -e
HELM_VERSION="v3.17.0"
BINARIES_DIR="src-tauri/binaries"
echo "Downloading helm binaries version ${HELM_VERSION}..."
mkdir -p "$BINARIES_DIR"
# Helm tarballs extract to {os}-{arch}/helm (or helm.exe on Windows)
echo "Downloading helm for Linux x86_64..."
TMPDIR=$(mktemp -d)
curl -L -o "$TMPDIR/helm-linux-amd64.tar.gz" \
"https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz"
tar -xzf "$TMPDIR/helm-linux-amd64.tar.gz" -C "$TMPDIR"
cp "$TMPDIR/linux-amd64/helm" "$BINARIES_DIR/helm-x86_64-unknown-linux-gnu"
rm -rf "$TMPDIR"
echo "Downloading helm for Linux aarch64..."
TMPDIR=$(mktemp -d)
curl -L -o "$TMPDIR/helm-linux-arm64.tar.gz" \
"https://get.helm.sh/helm-${HELM_VERSION}-linux-arm64.tar.gz"
tar -xzf "$TMPDIR/helm-linux-arm64.tar.gz" -C "$TMPDIR"
cp "$TMPDIR/linux-arm64/helm" "$BINARIES_DIR/helm-aarch64-unknown-linux-gnu"
rm -rf "$TMPDIR"
echo "Downloading helm for macOS x86_64..."
TMPDIR=$(mktemp -d)
curl -L -o "$TMPDIR/helm-darwin-amd64.tar.gz" \
"https://get.helm.sh/helm-${HELM_VERSION}-darwin-amd64.tar.gz"
tar -xzf "$TMPDIR/helm-darwin-amd64.tar.gz" -C "$TMPDIR"
cp "$TMPDIR/darwin-amd64/helm" "$BINARIES_DIR/helm-x86_64-apple-darwin"
rm -rf "$TMPDIR"
echo "Downloading helm for macOS aarch64..."
TMPDIR=$(mktemp -d)
curl -L -o "$TMPDIR/helm-darwin-arm64.tar.gz" \
"https://get.helm.sh/helm-${HELM_VERSION}-darwin-arm64.tar.gz"
tar -xzf "$TMPDIR/helm-darwin-arm64.tar.gz" -C "$TMPDIR"
cp "$TMPDIR/darwin-arm64/helm" "$BINARIES_DIR/helm-aarch64-apple-darwin"
rm -rf "$TMPDIR"
echo "Downloading helm for Windows x86_64..."
TMPDIR=$(mktemp -d)
curl -L -o "$TMPDIR/helm-windows-amd64.zip" \
"https://get.helm.sh/helm-${HELM_VERSION}-windows-amd64.zip"
unzip -q "$TMPDIR/helm-windows-amd64.zip" -d "$TMPDIR"
cp "$TMPDIR/windows-amd64/helm.exe" "$BINARIES_DIR/helm-x86_64-pc-windows-msvc.exe"
rm -rf "$TMPDIR"
# Make binaries executable
chmod +x "$BINARIES_DIR"/helm-*-linux-* "$BINARIES_DIR"/helm-*-darwin
echo "helm binaries downloaded successfully to $BINARIES_DIR"
echo "Total size:"
du -sh "$BINARIES_DIR"

View File

@ -330,6 +330,7 @@ pub async fn initiate_oauth(
let port_forwards = app_state.port_forwards.clone();
let refresh_registry = app_state.refresh_registry.clone();
let watchers = app_state.watchers.clone();
let log_streams = app_state.log_streams.clone();
tokio::spawn(async move {
let app_state_for_callback = AppState {
@ -343,6 +344,7 @@ pub async fn initiate_oauth(
port_forwards,
refresh_registry,
watchers,
log_streams,
};
while let Some(callback) = callback_rx.recv().await {
tracing::info!("Received OAuth callback for state: {}", callback.state);

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,7 @@ pub fn run() {
port_forwards: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
refresh_registry: Arc::new(tokio::sync::Mutex::new(crate::kube::RefreshRegistry::new())),
watchers: Arc::new(Mutex::new(std::collections::HashMap::new())),
log_streams: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())),
};
let stronghold_salt = format!(
"tftsr-stronghold-salt-v1-{:x}",
@ -232,6 +233,46 @@ pub fn run() {
commands::kube::rollback_deployment,
commands::kube::create_resource,
commands::kube::edit_resource,
// Phase 4: Additional Resource Discovery
commands::kube::list_replicationcontrollers,
commands::kube::list_poddisruptionbudgets,
commands::kube::list_priorityclasses,
commands::kube::list_runtimeclasses,
commands::kube::list_leases,
commands::kube::list_mutatingwebhookconfigurations,
commands::kube::list_validatingwebhookconfigurations,
commands::kube::list_endpoints,
commands::kube::list_endpointslices,
commands::kube::list_ingressclasses,
commands::kube::list_namespaces_resource,
commands::kube::list_crds,
commands::kube::list_custom_resources,
// Phase 5: Action Commands
commands::kube::force_delete_resource,
commands::kube::describe_resource,
commands::kube::get_resource_yaml,
commands::kube::attach_pod,
commands::kube::restart_statefulset,
commands::kube::restart_daemonset,
commands::kube::scale_statefulset,
commands::kube::scale_replicaset,
commands::kube::scale_replicationcontroller,
commands::kube::suspend_cronjob,
commands::kube::resume_cronjob,
commands::kube::trigger_cronjob,
commands::kube::create_namespace,
commands::kube::delete_namespace,
// Phase 6: Log Streaming
commands::kube::stream_pod_logs,
commands::kube::stop_log_stream,
// Phase 7: Helm Commands
commands::kube::helm_list_repos,
commands::kube::helm_add_repo,
commands::kube::helm_update_repos,
commands::kube::helm_search_repo,
commands::kube::helm_list_releases,
commands::kube::helm_uninstall,
commands::kube::helm_rollback,
])
.run(tauri::generate_context!())
.expect("Error running Troubleshooting and RCA Assistant application");

113
src-tauri/src/shell/helm.rs Normal file
View File

@ -0,0 +1,113 @@
// Helm Binary Management
//
// This module handles:
// - Locating the helm binary (bundled or system PATH)
use std::path::PathBuf;
use std::process::Command;
pub fn locate_helm() -> Result<PathBuf, String> {
// Strategy:
// 1. Check for bundled sidecar binary (platform-specific)
// 2. Fallback to system PATH (which helm)
// 3. Check common installation paths
let exe_suffix = if cfg!(windows) { ".exe" } else { "" };
// Try current directory (dev mode)
let local_helm = PathBuf::from(format!("helm{exe_suffix}"));
if local_helm.exists() {
return Ok(local_helm);
}
// Check for Tauri sidecar binary (production builds)
if let Ok(exe_path) = std::env::current_exe() {
if let Some(exe_dir) = exe_path.parent() {
let target = std::env::consts::ARCH.to_string()
+ "-"
+ if cfg!(target_os = "linux") {
"unknown-linux-gnu"
} else if cfg!(target_os = "macos") {
"apple-darwin"
} else if cfg!(target_os = "windows") {
"pc-windows-msvc"
} else {
"unknown"
};
let sidecar_name = format!("helm-{target}{exe_suffix}");
let sidecar_path = exe_dir.join(&sidecar_name);
if sidecar_path.exists() {
return Ok(sidecar_path);
}
// Also check Resources subdirectory (macOS .app bundle)
let resources_path = exe_dir.join("Resources").join(&sidecar_name);
if resources_path.exists() {
return Ok(resources_path);
}
}
}
// Check system PATH
#[cfg(not(target_os = "windows"))]
{
if let Ok(output) = Command::new("which").arg("helm").output() {
if output.status.success() {
let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
let path = PathBuf::from(path_str);
if path.exists() {
return Ok(path);
}
}
}
}
#[cfg(target_os = "windows")]
{
if let Ok(output) = Command::new("where").arg("helm").output() {
if output.status.success() {
let path_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
let path = PathBuf::from(path_str);
if path.exists() {
return Ok(path);
}
}
}
}
// Check common installation paths
let common_paths = [
"/usr/local/bin/helm",
"/usr/bin/helm",
"/opt/homebrew/bin/helm",
"/snap/bin/helm",
];
for path_str in &common_paths {
let path = PathBuf::from(path_str);
if path.exists() {
return Ok(path);
}
}
Err(
"helm binary not found. Please install helm or it will be bundled in production builds."
.to_string(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_locate_helm_finds_binary() {
let result = locate_helm();
if result.is_ok() {
assert!(result.unwrap().exists(), "helm path should exist if found");
}
// Test passes whether helm is found or not
}
}

View File

@ -1,5 +1,6 @@
pub mod classifier;
pub mod executor;
pub mod helm;
pub mod kubeconfig;
pub mod kubectl;
@ -8,5 +9,6 @@ mod tests;
pub use classifier::{ClassificationResult, CommandClassifier, CommandTier};
pub use executor::{execute_with_approval, CommandOutput};
pub use helm::locate_helm;
pub use kubeconfig::{auto_detect_kubeconfig, KubeconfigInfo};
pub use kubectl::{execute_kubectl, locate_kubectl};

View File

@ -99,6 +99,8 @@ pub struct AppState {
pub refresh_registry: Arc<TokioMutex<crate::kube::RefreshRegistry>>,
/// Resource watchers: unsubscribe_id -> receiver
pub watchers: Arc<Mutex<HashMap<String, tokio::sync::mpsc::Receiver<serde_json::Value>>>>,
/// Active pod log streaming tasks: stream_id -> abort handle
pub log_streams: Arc<TokioMutex<HashMap<String, tokio::task::AbortHandle>>>,
}
/// Determine the application data directory.