Merge pull request 'fix(kube): action namespace, race condition, stability, dark mode' (#86) from fix/kube-action-namespace-and-stability into master
Some checks failed
Auto Tag / autotag (push) Successful in 7s
Auto Tag / wiki-sync (push) Successful in 8s
Auto Tag / changelog (push) Successful in 1m23s
Test / frontend-tests (push) Successful in 1m47s
Test / frontend-typecheck (push) Successful in 1m49s
Auto Tag / build-macos-arm64 (push) Successful in 6m49s
Auto Tag / build-linux-amd64 (push) Successful in 10m2s
Auto Tag / build-windows-amd64 (push) Successful in 11m36s
Auto Tag / build-linux-arm64 (push) Successful in 11m52s
Test / rust-fmt-check (push) Successful in 16m20s
Test / rust-clippy (push) Successful in 17m56s
Test / rust-tests (push) Successful in 19m45s
Renovate / renovate (push) Failing after 18s
Some checks failed
Auto Tag / autotag (push) Successful in 7s
Auto Tag / wiki-sync (push) Successful in 8s
Auto Tag / changelog (push) Successful in 1m23s
Test / frontend-tests (push) Successful in 1m47s
Test / frontend-typecheck (push) Successful in 1m49s
Auto Tag / build-macos-arm64 (push) Successful in 6m49s
Auto Tag / build-linux-amd64 (push) Successful in 10m2s
Auto Tag / build-windows-amd64 (push) Successful in 11m36s
Auto Tag / build-linux-arm64 (push) Successful in 11m52s
Test / rust-fmt-check (push) Successful in 16m20s
Test / rust-clippy (push) Successful in 17m56s
Test / rust-tests (push) Successful in 19m45s
Renovate / renovate (push) Failing after 18s
Reviewed-on: #86
This commit is contained in:
commit
1d61e7ceb3
75
TICKET-kube-action-namespace-stability.md
Normal file
75
TICKET-kube-action-namespace-stability.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Ticket Summary — Kubernetes Action Namespace & Stability Fixes
|
||||||
|
|
||||||
|
**Branch**: `fix/kube-action-namespace-and-stability`
|
||||||
|
**PR**: https://gogs.tftsr.com/sarman/tftsr-devops_investigation/pulls/86
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Seven bugs in the Kubernetes management interface were identified via systematic debugging and resolved across 6 commits.
|
||||||
|
|
||||||
|
The most severe was a **temp kubeconfig race condition** in the Rust backend: every kubectl-based IPC command wrote a temp file to a static path derived only from `cluster_id`. Concurrent calls — triggered by rapid section or namespace switching — shared identical paths. `TempFileCleanup::drop()` on the first-to-finish call deleted the file while a concurrent kubectl process was still reading it. Errors were silently swallowed, leaving the UI showing stale/empty data. This was the root cause of "things stop loading after a few selection changes."
|
||||||
|
|
||||||
|
The second major class of bugs was **namespace `"all"` passed to targeted kubectl commands**. When the user selects "All Namespaces", `KubernetesPage` stores `selectedNamespace = "all"` and passes it as a prop to all list components. `loadResourceData` correctly converts `"all" → ""` for list fetching (which becomes `--all-namespaces` in Rust). However, action handlers inside list components (edit, delete, scale, logs, shell, attach) used the raw prop and forwarded `"all"` to `kubectl -n all`, producing "namespaces 'all' not found" errors.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [x] Rapid section/namespace switching no longer causes data to stop loading
|
||||||
|
- [x] Pod Logs loads successfully when "All Namespaces" is selected
|
||||||
|
- [x] Pod Shell, Attach, and Edit open and target the pod's actual namespace
|
||||||
|
- [x] Deployment, StatefulSet, DaemonSet, and all other workload action commands work under "All Namespaces"
|
||||||
|
- [x] Network, Config, Storage, and Access Control action commands work under "All Namespaces"
|
||||||
|
- [x] Workloads → Overview shows actual resource counts (not all-zero)
|
||||||
|
- [x] Cluster connection errors display a visible banner instead of failing silently
|
||||||
|
- [x] `connectClusterFromKubeconfigCmd` is only called once on mount, not twice
|
||||||
|
- [x] Dark mode — all text is readable; status indicators are visible
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Work Implemented
|
||||||
|
|
||||||
|
### Commit 1 — `fix(kube): unique temp kubeconfig paths`
|
||||||
|
**File**: `src-tauri/src/commands/kube.rs`
|
||||||
|
|
||||||
|
Added `KUBECONFIG_COUNTER: AtomicU64` and `unique_kubeconfig_path(cluster_id)` helper. Replaced all 74 static `temp_dir.join(format!("kubeconfig-{}-*.yaml"))` calls with the helper. Each invocation now gets a globally unique path, eliminating the race.
|
||||||
|
|
||||||
|
### Commit 2 — `fix(ui): replace hardcoded colors with semantic Tailwind vars`
|
||||||
|
**Files**: `src/components/Kubernetes/PortForwardList.tsx`, `src/components/Kubernetes/WorkloadOverview.tsx`
|
||||||
|
|
||||||
|
Replaced non-adaptive `text-gray-*` / `bg-gray-*` classes with `text-muted-foreground`, `bg-muted`, `border-border` — Tailwind CSS vars that correctly invert in dark mode.
|
||||||
|
|
||||||
|
### Commit 3 — `fix(kube): WorkloadOverview loads data; single connect; visible error`
|
||||||
|
**Files**: `src/pages/Kubernetes/KubernetesPage.tsx`, `tests/unit/KubernetesPage.test.tsx`
|
||||||
|
|
||||||
|
- Added `case "workloads_overview"` in `loadResourceData` that fetches pods + deployments + statefulsets + daemonsets + jobs + cronjobs via `Promise.allSettled` in parallel.
|
||||||
|
- Added `initializedRef` guard in `loadInitialData` to prevent double-connect when `selectedClusterId` changes.
|
||||||
|
- Connection errors now captured and shown as a dismissible banner.
|
||||||
|
|
||||||
|
### Commit 4 — `fix(kube): add namespace to PodInfo; pod actions use pod.namespace`
|
||||||
|
**Files**: `src-tauri/src/commands/kube.rs`, `src/lib/tauriCommands.ts`, `src/components/Kubernetes/PodList.tsx`, `tests/unit/PodList.test.tsx`
|
||||||
|
|
||||||
|
Added `namespace: String` to `PodInfo` Rust struct, extracted from `metadata.namespace` in `parse_pods_json`. Added `namespace: string` to TypeScript `PodInfo` interface. Updated all 6 action call sites in `PodList` to use `pod.namespace`.
|
||||||
|
|
||||||
|
### Commit 5 — `fix(kube): network/config/storage list actions use item.namespace`
|
||||||
|
**Files**: `ServiceList`, `IngressList`, `ConfigMapList`, `SecretList`, `HPAList`, `PVCList`, `ServiceAccountList`, `RoleList`, `RoleBindingList`, `NetworkPolicyList`, `ResourceQuotaList`, `LimitRangeList` + `tests/unit/NamespaceActionFix.test.tsx`
|
||||||
|
|
||||||
|
12 components fixed. 24 new tests (2 per component).
|
||||||
|
|
||||||
|
### Commit 6 — `fix(kube): workload list actions use item.namespace not filter prop`
|
||||||
|
**Files**: `DeploymentList`, `StatefulSetList`, `DaemonSetList`, `ReplicaSetList`, `JobList`, `CronJobList` + `tests/unit/WorkloadListActions.test.tsx`
|
||||||
|
|
||||||
|
6 components fixed. 21 new tests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Needed
|
||||||
|
|
||||||
|
1. **Automated**: `cargo test` → 364 pass; `npm run test:run` → 325 pass; `npx tsc --noEmit` → 0; `npx eslint . --max-warnings 0` → 0; `cargo clippy -- -D warnings` → 0; `cargo fmt --check` → clean
|
||||||
|
2. **Manual — race condition**: With a live cluster, rapidly switch between Pods → Deployments → Services → ConfigMaps several times. Data should load reliably every time.
|
||||||
|
3. **Manual — pod actions**: Select "All Namespaces". Open pod action menu → Logs → should fetch without error. Shell/Attach → modals open, exec targets correct namespace. Edit → YAML editor opens.
|
||||||
|
4. **Manual — overview**: Navigate to Workloads → Overview. Cards should show actual pod/deployment/etc. counts.
|
||||||
|
5. **Manual — error banner**: Configure an invalid kubeconfig. Navigate to Kubernetes page. A red banner should appear with the connection error. Clicking Dismiss hides it.
|
||||||
|
6. **Manual — dark mode**: Switch to dark theme. All text in Kubernetes pages (sidebar, tables, status indicators) should be readable with good contrast.
|
||||||
@ -20,6 +20,13 @@ lazy_static! {
|
|||||||
static ref NAME_PATTERN_REGEX: Regex = Regex::new(r"^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$").unwrap();
|
static ref NAME_PATTERN_REGEX: Regex = Regex::new(r"^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static KUBECONFIG_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||||
|
|
||||||
|
fn unique_kubeconfig_path(cluster_id: impl AsRef<str>) -> std::path::PathBuf {
|
||||||
|
let n = KUBECONFIG_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
std::env::temp_dir().join(format!("kubeconfig-{}-{}.yaml", cluster_id.as_ref(), n))
|
||||||
|
}
|
||||||
|
|
||||||
struct TempFileCleanup(std::path::PathBuf);
|
struct TempFileCleanup(std::path::PathBuf);
|
||||||
impl Drop for TempFileCleanup {
|
impl Drop for TempFileCleanup {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
@ -84,6 +91,7 @@ pub struct PortForwardResponse {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct PodInfo {
|
pub struct PodInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub namespace: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub ready: String,
|
pub ready: String,
|
||||||
pub age: String,
|
pub age: String,
|
||||||
@ -319,8 +327,7 @@ pub async fn test_kubectl_connection(
|
|||||||
(cluster.kubeconfig_content.clone(), cluster.context.clone())
|
(cluster.kubeconfig_content.clone(), cluster.context.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-diag.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content.as_ref())
|
write_secure_temp_file(&temp_path, kubeconfig_content.as_ref())
|
||||||
@ -467,8 +474,7 @@ pub async fn test_cluster_connection(
|
|||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
// Write kubeconfig to temp file and ensure cleanup even on panic
|
// Write kubeconfig to temp file and ensure cleanup even on panic
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(&cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -517,8 +523,7 @@ pub async fn discover_pods(
|
|||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
// Write kubeconfig to temp file and ensure cleanup even on panic
|
// Write kubeconfig to temp file and ensure cleanup even on panic
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-pods.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -647,8 +652,7 @@ pub async fn start_port_forward(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Write kubeconfig to temp file
|
// Write kubeconfig to temp file
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(&request.cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}.yaml", request.cluster_id));
|
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content.as_ref())
|
write_secure_temp_file(&temp_path, kubeconfig_content.as_ref())
|
||||||
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
||||||
@ -969,8 +973,7 @@ pub async fn list_namespaces(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-namespaces.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1052,8 +1055,7 @@ pub async fn list_pods(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-pods.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1106,6 +1108,13 @@ fn parse_pods_json(json_str: &str) -> Result<Vec<PodInfo>, String> {
|
|||||||
.unwrap_or("unknown")
|
.unwrap_or("unknown")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
let namespace = item
|
||||||
|
.get("metadata")
|
||||||
|
.and_then(|m| m.get("namespace"))
|
||||||
|
.and_then(|n| n.as_str())
|
||||||
|
.unwrap_or("default")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
let status = item
|
let status = item
|
||||||
.get("status")
|
.get("status")
|
||||||
.and_then(|s| s.get("phase"))
|
.and_then(|s| s.get("phase"))
|
||||||
@ -1152,6 +1161,7 @@ fn parse_pods_json(json_str: &str) -> Result<Vec<PodInfo>, String> {
|
|||||||
|
|
||||||
pods.push(PodInfo {
|
pods.push(PodInfo {
|
||||||
name,
|
name,
|
||||||
|
namespace,
|
||||||
status,
|
status,
|
||||||
ready,
|
ready,
|
||||||
age,
|
age,
|
||||||
@ -1176,8 +1186,7 @@ pub async fn list_services(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-services.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1335,8 +1344,7 @@ pub async fn list_deployments(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-deployments.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1470,8 +1478,7 @@ pub async fn list_statefulsets(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-statefulsets.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1589,8 +1596,7 @@ pub async fn list_daemonsets(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-daemonsets.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1758,8 +1764,7 @@ pub async fn get_pod_logs(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-logs.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1808,8 +1813,7 @@ pub async fn scale_deployment(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-scale.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1856,8 +1860,7 @@ pub async fn restart_deployment(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-restart.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1904,8 +1907,7 @@ pub async fn delete_resource(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-delete.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -1953,8 +1955,7 @@ pub async fn exec_pod(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-exec.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -2232,8 +2233,7 @@ pub async fn list_replicasets(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-replicasets.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -2351,8 +2351,7 @@ pub async fn list_jobs(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-jobs.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -2508,8 +2507,7 @@ pub async fn list_cronjobs(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-cronjobs.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -2636,8 +2634,7 @@ pub async fn list_configmaps(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-configmaps.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -2735,8 +2732,7 @@ pub async fn list_secrets(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-secrets.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -2840,8 +2836,7 @@ pub async fn list_nodes(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-nodes.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3029,8 +3024,7 @@ pub async fn list_events(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-events.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3159,8 +3153,7 @@ pub async fn list_ingresses(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-ingresses.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3286,8 +3279,7 @@ pub async fn list_persistentvolumeclaims(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-pvcs.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3417,8 +3409,7 @@ pub async fn list_persistentvolumes(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-pvs.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3544,8 +3535,7 @@ pub async fn list_serviceaccounts(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-sas.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3643,8 +3633,7 @@ pub async fn list_roles(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-roles.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3734,8 +3723,7 @@ pub async fn list_clusterroles(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-clusterroles.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3810,8 +3798,7 @@ pub async fn list_rolebindings(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-rolebindings.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3909,11 +3896,7 @@ pub async fn list_clusterrolebindings(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!(
|
|
||||||
"kubeconfig-{}-clusterrolebindings.yaml",
|
|
||||||
cluster_id
|
|
||||||
));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -3999,8 +3982,7 @@ pub async fn list_horizontalpodautoscalers(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-hpas.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4118,8 +4100,7 @@ pub async fn list_storageclasses(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-storageclasses.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4224,8 +4205,7 @@ pub async fn list_networkpolicies(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-networkpolicies.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4336,8 +4316,7 @@ pub async fn list_resourcequotas(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-resourcequotas.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4458,8 +4437,7 @@ pub async fn list_limitranges(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-limitranges.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4558,8 +4536,7 @@ pub async fn cordon_node(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-cordon.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4600,8 +4577,7 @@ pub async fn uncordon_node(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-uncordon.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4642,8 +4618,7 @@ pub async fn drain_node(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-drain.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4688,8 +4663,7 @@ pub async fn rollback_deployment(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-rollback.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4736,8 +4710,7 @@ pub async fn create_resource(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-create.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -4800,8 +4773,7 @@ pub async fn edit_resource(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-edit.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -5050,8 +5022,7 @@ pub async fn list_replicationcontrollers(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-rcs.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -5175,8 +5146,7 @@ pub async fn list_poddisruptionbudgets(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-pdbs.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -5291,8 +5261,7 @@ pub async fn list_priorityclasses(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-priorityclasses.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -5389,8 +5358,7 @@ pub async fn list_runtimeclasses(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-runtimeclasses.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -5475,8 +5443,7 @@ pub async fn list_leases(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-leases.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -5578,8 +5545,7 @@ pub async fn list_mutatingwebhookconfigurations(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-mwhc.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -5669,8 +5635,7 @@ pub async fn list_validatingwebhookconfigurations(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-vwhc.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -5761,8 +5726,7 @@ pub async fn list_endpoints(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-endpoints.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -5877,8 +5841,7 @@ pub async fn list_endpointslices(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-endpointslices.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6000,8 +5963,7 @@ pub async fn list_ingressclasses(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-ingressclasses.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6101,8 +6063,7 @@ pub async fn list_namespaces_resource(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-nsres.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6187,8 +6148,7 @@ pub async fn list_crds(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-crds.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6312,8 +6272,7 @@ pub async fn list_custom_resources(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(&cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-cr.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6439,8 +6398,7 @@ pub async fn force_delete_resource(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(&cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-force-delete.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6498,8 +6456,7 @@ pub async fn describe_resource(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-describe.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6553,8 +6510,7 @@ pub async fn get_resource_yaml(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-getyaml.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6608,8 +6564,7 @@ pub async fn attach_pod(
|
|||||||
|
|
||||||
let session_id = uuid::Uuid::now_v7().to_string();
|
let session_id = uuid::Uuid::now_v7().to_string();
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(&cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-attach.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6676,8 +6631,7 @@ pub async fn restart_statefulset(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-restart-sts.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6724,8 +6678,7 @@ pub async fn restart_daemonset(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-restart-ds.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6773,8 +6726,7 @@ pub async fn scale_statefulset(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-scale-sts.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6822,8 +6774,7 @@ pub async fn scale_replicaset(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-scale-rs.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6871,8 +6822,7 @@ pub async fn scale_replicationcontroller(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-scale-rc.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6919,8 +6869,7 @@ pub async fn suspend_cronjob(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-suspend-cj.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -6968,8 +6917,7 @@ pub async fn resume_cronjob(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-resume-cj.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -7017,8 +6965,7 @@ pub async fn trigger_cronjob(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-trigger-cj.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -7069,8 +7016,7 @@ pub async fn create_namespace(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(&cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-create-ns.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -7116,8 +7062,7 @@ pub async fn delete_namespace(
|
|||||||
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
let kubeconfig_content = cluster.kubeconfig_content.as_ref();
|
||||||
let context = &cluster.context;
|
let context = &cluster.context;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(&cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-delete-ns.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_content)
|
write_secure_temp_file(&temp_path, kubeconfig_content)
|
||||||
@ -7189,8 +7134,7 @@ pub async fn stream_pod_logs(
|
|||||||
|
|
||||||
let (kubeconfig_arc, context) = kubeconfig_content;
|
let (kubeconfig_arc, context) = kubeconfig_content;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(config.cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-stream.yaml", config.cluster_id));
|
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_arc.as_ref())
|
write_secure_temp_file(&temp_path, kubeconfig_arc.as_ref())
|
||||||
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
.map_err(|e| format!("Failed to write kubeconfig temp file: {e}"))?;
|
||||||
@ -7514,8 +7458,7 @@ pub async fn helm_list_releases(
|
|||||||
|
|
||||||
let (kubeconfig_arc, context) = kubeconfig_content;
|
let (kubeconfig_arc, context) = kubeconfig_content;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-helm-list.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_arc.as_ref())
|
write_secure_temp_file(&temp_path, kubeconfig_arc.as_ref())
|
||||||
@ -7638,8 +7581,7 @@ pub async fn helm_uninstall(
|
|||||||
|
|
||||||
let (kubeconfig_arc, context) = kubeconfig_content;
|
let (kubeconfig_arc, context) = kubeconfig_content;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(&cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-helm-uninstall.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_arc.as_ref())
|
write_secure_temp_file(&temp_path, kubeconfig_arc.as_ref())
|
||||||
@ -7690,8 +7632,7 @@ pub async fn helm_rollback(
|
|||||||
|
|
||||||
let (kubeconfig_arc, context) = kubeconfig_content;
|
let (kubeconfig_arc, context) = kubeconfig_content;
|
||||||
|
|
||||||
let temp_dir = std::env::temp_dir();
|
let temp_path = unique_kubeconfig_path(&cluster_id);
|
||||||
let temp_path = temp_dir.join(format!("kubeconfig-{}-helm-rollback.yaml", cluster_id));
|
|
||||||
let _cleanup = TempFileCleanup(temp_path.clone());
|
let _cleanup = TempFileCleanup(temp_path.clone());
|
||||||
|
|
||||||
write_secure_temp_file(&temp_path, kubeconfig_arc.as_ref())
|
write_secure_temp_file(&temp_path, kubeconfig_arc.as_ref())
|
||||||
@ -7909,4 +7850,14 @@ mod new_command_tests {
|
|||||||
let result = parse_crds_json(json).unwrap();
|
let result = parse_crds_json(json).unwrap();
|
||||||
assert!(result.is_empty());
|
assert!(result.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unique_kubeconfig_path_produces_distinct_paths() {
|
||||||
|
let path1 = unique_kubeconfig_path("test-cluster");
|
||||||
|
let path2 = unique_kubeconfig_path("test-cluster");
|
||||||
|
assert_ne!(
|
||||||
|
path1, path2,
|
||||||
|
"successive calls must return distinct paths to prevent concurrent-call race conditions"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ type ActiveModal =
|
|||||||
| { type: "delete"; cm: ConfigMapInfo }
|
| { type: "delete"; cm: ConfigMapInfo }
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export function ConfigMapList({ configmaps, clusterId, namespace, onRefresh }: ConfigMapListProps) {
|
export function ConfigMapList({ configmaps, clusterId, namespace: _namespace, onRefresh }: ConfigMapListProps) {
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -27,7 +27,7 @@ export function ConfigMapList({ configmaps, clusterId, namespace, onRefresh }: C
|
|||||||
const openEdit = async (cm: ConfigMapInfo) => {
|
const openEdit = async (cm: ConfigMapInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(clusterId, "configmaps", namespace, cm.name);
|
const yaml = await getResourceYamlCmd(clusterId, "configmaps", cm.namespace, cm.name);
|
||||||
setActiveModal({ type: "edit", cm, yaml });
|
setActiveModal({ type: "edit", cm, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -38,7 +38,7 @@ export function ConfigMapList({ configmaps, clusterId, namespace, onRefresh }: C
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(clusterId, "configmaps", namespace, activeModal.cm.name);
|
await deleteResourceCmd(clusterId, "configmaps", activeModal.cm.namespace, activeModal.cm.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -104,7 +104,7 @@ export function ConfigMapList({ configmaps, clusterId, namespace, onRefresh }: C
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.cm.namespace}
|
||||||
resourceType="configmaps"
|
resourceType="configmaps"
|
||||||
resourceName={activeModal.cm.name}
|
resourceName={activeModal.cm.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -31,12 +31,9 @@ export function CronJobList({
|
|||||||
cronJobs,
|
cronJobs,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: CronJobListProps) {
|
}: CronJobListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -44,7 +41,7 @@ export function CronJobList({
|
|||||||
const openEdit = async (cj: CronJobInfo) => {
|
const openEdit = async (cj: CronJobInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "cronjobs", ns, cj.name);
|
const yaml = await getResourceYamlCmd(cid, "cronjobs", cj.namespace, cj.name);
|
||||||
setActiveModal({ type: "edit", cj, yaml });
|
setActiveModal({ type: "edit", cj, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -54,7 +51,7 @@ export function CronJobList({
|
|||||||
const handleSuspend = async (cj: CronJobInfo) => {
|
const handleSuspend = async (cj: CronJobInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
await suspendCronjobCmd(cid, ns, cj.name);
|
await suspendCronjobCmd(cid, cj.namespace, cj.name);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -64,7 +61,7 @@ export function CronJobList({
|
|||||||
const handleResume = async (cj: CronJobInfo) => {
|
const handleResume = async (cj: CronJobInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
await resumeCronjobCmd(cid, ns, cj.name);
|
await resumeCronjobCmd(cid, cj.namespace, cj.name);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -74,7 +71,7 @@ export function CronJobList({
|
|||||||
const handleTrigger = async (cj: CronJobInfo) => {
|
const handleTrigger = async (cj: CronJobInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
await triggerCronjobCmd(cid, ns, cj.name);
|
await triggerCronjobCmd(cid, cj.namespace, cj.name);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -85,7 +82,7 @@ export function CronJobList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "cronjobs", ns, activeModal.cj.name);
|
await deleteResourceCmd(cid, "cronjobs", activeModal.cj.namespace, activeModal.cj.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -183,7 +180,7 @@ export function CronJobList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.cj.namespace}
|
||||||
resourceType="cronjobs"
|
resourceType="cronjobs"
|
||||||
resourceName={activeModal.cj.name}
|
resourceName={activeModal.cj.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ type ActiveModal =
|
|||||||
| { type: "delete"; ds: DaemonSetInfo }
|
| { type: "delete"; ds: DaemonSetInfo }
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: DaemonSetListProps) {
|
export function DaemonSetList({ daemonsets, clusterId, namespace: _namespace, onRefresh }: DaemonSetListProps) {
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isActing, setIsActing] = useState(false);
|
const [isActing, setIsActing] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -32,7 +32,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
|
|||||||
const openEdit = async (ds: DaemonSetInfo) => {
|
const openEdit = async (ds: DaemonSetInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(clusterId, "daemonsets", namespace, ds.name);
|
const yaml = await getResourceYamlCmd(clusterId, "daemonsets", ds.namespace, ds.name);
|
||||||
setActiveModal({ type: "edit", ds, yaml });
|
setActiveModal({ type: "edit", ds, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -43,7 +43,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
|
|||||||
if (activeModal?.type !== "restart") return;
|
if (activeModal?.type !== "restart") return;
|
||||||
setIsActing(true);
|
setIsActing(true);
|
||||||
try {
|
try {
|
||||||
await restartDaemonsetCmd(clusterId, namespace, activeModal.ds.name);
|
await restartDaemonsetCmd(clusterId, activeModal.ds.namespace, activeModal.ds.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -57,7 +57,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsActing(true);
|
setIsActing(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(clusterId, "daemonsets", namespace, activeModal.ds.name);
|
await deleteResourceCmd(clusterId, "daemonsets", activeModal.ds.namespace, activeModal.ds.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -146,7 +146,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.ds.namespace}
|
||||||
resourceType="daemonsets"
|
resourceType="daemonsets"
|
||||||
resourceName={activeModal.ds.name}
|
resourceName={activeModal.ds.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -29,7 +29,7 @@ type ActiveModal =
|
|||||||
| { type: "delete"; deployment: DeploymentInfo }
|
| { type: "delete"; deployment: DeploymentInfo }
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export function DeploymentList({ deployments, clusterId, namespace, onRefresh }: DeploymentListProps) {
|
export function DeploymentList({ deployments, clusterId, namespace: _namespace, onRefresh }: DeploymentListProps) {
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isActing, setIsActing] = useState(false);
|
const [isActing, setIsActing] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -37,7 +37,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
|||||||
const openEdit = async (deployment: DeploymentInfo) => {
|
const openEdit = async (deployment: DeploymentInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(clusterId, "deployments", namespace, deployment.name);
|
const yaml = await getResourceYamlCmd(clusterId, "deployments", deployment.namespace, deployment.name);
|
||||||
setActiveModal({ type: "edit", deployment, yaml });
|
setActiveModal({ type: "edit", deployment, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -48,7 +48,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
|||||||
if (activeModal?.type !== "restart") return;
|
if (activeModal?.type !== "restart") return;
|
||||||
setIsActing(true);
|
setIsActing(true);
|
||||||
try {
|
try {
|
||||||
await restartDeploymentCmd(clusterId, namespace, activeModal.deployment.name);
|
await restartDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -62,7 +62,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
|||||||
if (activeModal?.type !== "rollback") return;
|
if (activeModal?.type !== "rollback") return;
|
||||||
setIsActing(true);
|
setIsActing(true);
|
||||||
try {
|
try {
|
||||||
await rollbackDeploymentCmd(clusterId, namespace, activeModal.deployment.name);
|
await rollbackDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -76,7 +76,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsActing(true);
|
setIsActing(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(clusterId, "deployments", namespace, activeModal.deployment.name);
|
await deleteResourceCmd(clusterId, "deployments", activeModal.deployment.namespace, activeModal.deployment.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -165,7 +165,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
|||||||
resourceName={activeModal.deployment.name}
|
resourceName={activeModal.deployment.name}
|
||||||
currentReplicas={activeModal.deployment.replicas}
|
currentReplicas={activeModal.deployment.replicas}
|
||||||
onScale={(replicas) =>
|
onScale={(replicas) =>
|
||||||
scaleDeploymentCmd(clusterId, namespace, activeModal.deployment.name, replicas).then(() => {
|
scaleDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name, replicas).then(() => {
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
})
|
})
|
||||||
@ -201,7 +201,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.deployment.namespace}
|
||||||
resourceType="deployments"
|
resourceType="deployments"
|
||||||
resourceName={activeModal.deployment.name}
|
resourceName={activeModal.deployment.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -25,12 +25,9 @@ export function HPAList({
|
|||||||
hpas,
|
hpas,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: HPAListProps) {
|
}: HPAListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -38,7 +35,7 @@ export function HPAList({
|
|||||||
const openEdit = async (hpa: HorizontalPodAutoscalerInfo) => {
|
const openEdit = async (hpa: HorizontalPodAutoscalerInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "horizontalpodautoscalers", ns, hpa.name);
|
const yaml = await getResourceYamlCmd(cid, "horizontalpodautoscalers", hpa.namespace, hpa.name);
|
||||||
setActiveModal({ type: "edit", hpa, yaml });
|
setActiveModal({ type: "edit", hpa, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -49,7 +46,7 @@ export function HPAList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "horizontalpodautoscalers", ns, activeModal.hpa.name);
|
await deleteResourceCmd(cid, "horizontalpodautoscalers", activeModal.hpa.namespace, activeModal.hpa.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -121,7 +118,7 @@ export function HPAList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.hpa.namespace}
|
||||||
resourceType="horizontalpodautoscalers"
|
resourceType="horizontalpodautoscalers"
|
||||||
resourceName={activeModal.hpa.name}
|
resourceName={activeModal.hpa.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -25,12 +25,9 @@ export function IngressList({
|
|||||||
ingresses,
|
ingresses,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: IngressListProps) {
|
}: IngressListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -38,7 +35,7 @@ export function IngressList({
|
|||||||
const openEdit = async (ingress: IngressInfo) => {
|
const openEdit = async (ingress: IngressInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "ingresses", ns, ingress.name);
|
const yaml = await getResourceYamlCmd(cid, "ingresses", ingress.namespace, ingress.name);
|
||||||
setActiveModal({ type: "edit", ingress, yaml });
|
setActiveModal({ type: "edit", ingress, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -49,7 +46,7 @@ export function IngressList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "ingresses", ns, activeModal.ingress.name);
|
await deleteResourceCmd(cid, "ingresses", activeModal.ingress.namespace, activeModal.ingress.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -119,7 +116,7 @@ export function IngressList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.ingress.namespace}
|
||||||
resourceType="ingresses"
|
resourceType="ingresses"
|
||||||
resourceName={activeModal.ingress.name}
|
resourceName={activeModal.ingress.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -25,12 +25,9 @@ export function JobList({
|
|||||||
jobs,
|
jobs,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: JobListProps) {
|
}: JobListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -38,7 +35,7 @@ export function JobList({
|
|||||||
const openEdit = async (job: JobInfo) => {
|
const openEdit = async (job: JobInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "jobs", ns, job.name);
|
const yaml = await getResourceYamlCmd(cid, "jobs", job.namespace, job.name);
|
||||||
setActiveModal({ type: "edit", job, yaml });
|
setActiveModal({ type: "edit", job, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -49,7 +46,7 @@ export function JobList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "jobs", ns, activeModal.job.name);
|
await deleteResourceCmd(cid, "jobs", activeModal.job.namespace, activeModal.job.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -123,7 +120,7 @@ export function JobList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.job.namespace}
|
||||||
resourceType="jobs"
|
resourceType="jobs"
|
||||||
resourceName={activeModal.job.name}
|
resourceName={activeModal.job.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ type ActiveModal =
|
|||||||
| { type: "delete"; lr: LimitRangeInfo }
|
| { type: "delete"; lr: LimitRangeInfo }
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export function LimitRangeList({ limitranges, clusterId, namespace, onRefresh }: LimitRangeListProps) {
|
export function LimitRangeList({ limitranges, clusterId, namespace: _namespace, onRefresh }: LimitRangeListProps) {
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -27,7 +27,7 @@ export function LimitRangeList({ limitranges, clusterId, namespace, onRefresh }:
|
|||||||
const openEdit = async (lr: LimitRangeInfo) => {
|
const openEdit = async (lr: LimitRangeInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(clusterId, "limitranges", namespace, lr.name);
|
const yaml = await getResourceYamlCmd(clusterId, "limitranges", lr.namespace, lr.name);
|
||||||
setActiveModal({ type: "edit", lr, yaml });
|
setActiveModal({ type: "edit", lr, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -38,7 +38,7 @@ export function LimitRangeList({ limitranges, clusterId, namespace, onRefresh }:
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(clusterId, "limitranges", namespace, activeModal.lr.name);
|
await deleteResourceCmd(clusterId, "limitranges", activeModal.lr.namespace, activeModal.lr.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -104,7 +104,7 @@ export function LimitRangeList({ limitranges, clusterId, namespace, onRefresh }:
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.lr.namespace}
|
||||||
resourceType="limitranges"
|
resourceType="limitranges"
|
||||||
resourceName={activeModal.lr.name}
|
resourceName={activeModal.lr.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ type ActiveModal =
|
|||||||
| { type: "delete"; np: NetworkPolicyInfo }
|
| { type: "delete"; np: NetworkPolicyInfo }
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export function NetworkPolicyList({ networkpolicies, clusterId, namespace, onRefresh }: NetworkPolicyListProps) {
|
export function NetworkPolicyList({ networkpolicies, clusterId, namespace: _namespace, onRefresh }: NetworkPolicyListProps) {
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -27,7 +27,7 @@ export function NetworkPolicyList({ networkpolicies, clusterId, namespace, onRef
|
|||||||
const openEdit = async (np: NetworkPolicyInfo) => {
|
const openEdit = async (np: NetworkPolicyInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(clusterId, "networkpolicies", namespace, np.name);
|
const yaml = await getResourceYamlCmd(clusterId, "networkpolicies", np.namespace, np.name);
|
||||||
setActiveModal({ type: "edit", np, yaml });
|
setActiveModal({ type: "edit", np, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -38,7 +38,7 @@ export function NetworkPolicyList({ networkpolicies, clusterId, namespace, onRef
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(clusterId, "networkpolicies", namespace, activeModal.np.name);
|
await deleteResourceCmd(clusterId, "networkpolicies", activeModal.np.namespace, activeModal.np.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -106,7 +106,7 @@ export function NetworkPolicyList({ networkpolicies, clusterId, namespace, onRef
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.np.namespace}
|
||||||
resourceType="networkpolicies"
|
resourceType="networkpolicies"
|
||||||
resourceName={activeModal.np.name}
|
resourceName={activeModal.np.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -25,12 +25,9 @@ export function PVCList({
|
|||||||
pvcs,
|
pvcs,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: PVCListProps) {
|
}: PVCListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -38,7 +35,7 @@ export function PVCList({
|
|||||||
const openEdit = async (pvc: PersistentVolumeClaimInfo) => {
|
const openEdit = async (pvc: PersistentVolumeClaimInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "persistentvolumeclaims", ns, pvc.name);
|
const yaml = await getResourceYamlCmd(cid, "persistentvolumeclaims", pvc.namespace, pvc.name);
|
||||||
setActiveModal({ type: "edit", pvc, yaml });
|
setActiveModal({ type: "edit", pvc, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -49,7 +46,7 @@ export function PVCList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "persistentvolumeclaims", ns, activeModal.pvc.name);
|
await deleteResourceCmd(cid, "persistentvolumeclaims", activeModal.pvc.namespace, activeModal.pvc.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -121,7 +118,7 @@ export function PVCList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.pvc.namespace}
|
||||||
resourceType="persistentvolumeclaims"
|
resourceType="persistentvolumeclaims"
|
||||||
resourceName={activeModal.pvc.name}
|
resourceName={activeModal.pvc.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -32,6 +32,9 @@ export function PodList({ pods, clusterId, namespace, onRefresh }: PodListProps)
|
|||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [editError, setEditError] = useState<string | null>(null);
|
const [editError, setEditError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// namespace prop is retained for API compatibility (parent uses it to drive list fetches)
|
||||||
|
void namespace;
|
||||||
|
|
||||||
const getPodStatusColor = (status: string) => {
|
const getPodStatusColor = (status: string) => {
|
||||||
switch (status.toLowerCase()) {
|
switch (status.toLowerCase()) {
|
||||||
case "running":
|
case "running":
|
||||||
@ -52,7 +55,7 @@ export function PodList({ pods, clusterId, namespace, onRefresh }: PodListProps)
|
|||||||
const openEdit = async (pod: PodInfo) => {
|
const openEdit = async (pod: PodInfo) => {
|
||||||
setEditError(null);
|
setEditError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(clusterId, "pods", namespace, pod.name);
|
const yaml = await getResourceYamlCmd(clusterId, "pods", pod.namespace, pod.name);
|
||||||
setActiveModal({ type: "edit", pod, yaml });
|
setActiveModal({ type: "edit", pod, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setEditError(err instanceof Error ? err.message : String(err));
|
setEditError(err instanceof Error ? err.message : String(err));
|
||||||
@ -65,9 +68,9 @@ export function PodList({ pods, clusterId, namespace, onRefresh }: PodListProps)
|
|||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
if (force) {
|
if (force) {
|
||||||
await forceDeleteResourceCmd(clusterId, "pods", namespace, modal.pod.name);
|
await forceDeleteResourceCmd(clusterId, "pods", modal.pod.namespace, modal.pod.name);
|
||||||
} else {
|
} else {
|
||||||
await deleteResourceCmd(clusterId, "pods", namespace, modal.pod.name);
|
await deleteResourceCmd(clusterId, "pods", modal.pod.namespace, modal.pod.name);
|
||||||
}
|
}
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
@ -167,7 +170,7 @@ export function PodList({ pods, clusterId, namespace, onRefresh }: PodListProps)
|
|||||||
open
|
open
|
||||||
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
|
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.pod.namespace}
|
||||||
podName={activeModal.pod.name}
|
podName={activeModal.pod.name}
|
||||||
containers={activeModal.pod.containers}
|
containers={activeModal.pod.containers}
|
||||||
/>
|
/>
|
||||||
@ -178,7 +181,7 @@ export function PodList({ pods, clusterId, namespace, onRefresh }: PodListProps)
|
|||||||
open
|
open
|
||||||
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
|
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.pod.namespace}
|
||||||
podName={activeModal.pod.name}
|
podName={activeModal.pod.name}
|
||||||
containers={activeModal.pod.containers}
|
containers={activeModal.pod.containers}
|
||||||
/>
|
/>
|
||||||
@ -189,7 +192,7 @@ export function PodList({ pods, clusterId, namespace, onRefresh }: PodListProps)
|
|||||||
open
|
open
|
||||||
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
|
onOpenChange={(o) => { if (!o) setActiveModal(null); }}
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.pod.namespace}
|
||||||
podName={activeModal.pod.name}
|
podName={activeModal.pod.name}
|
||||||
containers={activeModal.pod.containers}
|
containers={activeModal.pod.containers}
|
||||||
/>
|
/>
|
||||||
@ -199,7 +202,7 @@ export function PodList({ pods, clusterId, namespace, onRefresh }: PodListProps)
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.pod.namespace}
|
||||||
resourceType="pods"
|
resourceType="pods"
|
||||||
resourceName={activeModal.pod.name}
|
resourceName={activeModal.pod.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export function PortForwardList({ portForwards, onStart, onStop, onDelete }: Por
|
|||||||
case "active":
|
case "active":
|
||||||
return "bg-green-500/15 text-green-600 dark:text-green-400 border-green-500/20";
|
return "bg-green-500/15 text-green-600 dark:text-green-400 border-green-500/20";
|
||||||
case "stopped":
|
case "stopped":
|
||||||
return "bg-gray-500/15 text-gray-600 dark:text-gray-400 border-gray-500/20";
|
return "bg-muted text-muted-foreground border-border";
|
||||||
case "error":
|
case "error":
|
||||||
return "bg-red-500/15 text-red-600 dark:text-red-400 border-red-500/20";
|
return "bg-red-500/15 text-red-600 dark:text-red-400 border-red-500/20";
|
||||||
default:
|
default:
|
||||||
@ -95,7 +95,7 @@ export function PortForwardList({ portForwards, onStart, onStop, onDelete }: Por
|
|||||||
</p>
|
</p>
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<span>Container Ports: {pf.container_ports.join(", ")}</span>
|
<span>Container Ports: {pf.container_ports.join(", ")}</span>
|
||||||
<span className="text-gray-300 dark:text-gray-600">|</span>
|
<span className="text-muted-foreground/50">|</span>
|
||||||
<span>Local Ports: {pf.local_ports.some(p => p > 0) ? pf.local_ports.join(", ") : "pending"}</span>
|
<span>Local Ports: {pf.local_ports.some(p => p > 0) ? pf.local_ports.join(", ") : "pending"}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -31,12 +31,9 @@ export function ReplicaSetList({
|
|||||||
replicaSets,
|
replicaSets,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: ReplicaSetListProps) {
|
}: ReplicaSetListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isActing, setIsActing] = useState(false);
|
const [isActing, setIsActing] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -44,7 +41,7 @@ export function ReplicaSetList({
|
|||||||
const openEdit = async (rs: ReplicaSetInfo) => {
|
const openEdit = async (rs: ReplicaSetInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "replicasets", ns, rs.name);
|
const yaml = await getResourceYamlCmd(cid, "replicasets", rs.namespace, rs.name);
|
||||||
setActiveModal({ type: "edit", rs, yaml });
|
setActiveModal({ type: "edit", rs, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -55,7 +52,7 @@ export function ReplicaSetList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsActing(true);
|
setIsActing(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "replicasets", ns, activeModal.rs.name);
|
await deleteResourceCmd(cid, "replicasets", activeModal.rs.namespace, activeModal.rs.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -138,7 +135,7 @@ export function ReplicaSetList({
|
|||||||
resourceName={activeModal.rs.name}
|
resourceName={activeModal.rs.name}
|
||||||
currentReplicas={activeModal.rs.replicas}
|
currentReplicas={activeModal.rs.replicas}
|
||||||
onScale={(replicas) =>
|
onScale={(replicas) =>
|
||||||
scaleReplicasetCmd(cid, ns, activeModal.rs.name, replicas).then(() => {
|
scaleReplicasetCmd(cid, activeModal.rs.namespace, activeModal.rs.name, replicas).then(() => {
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
})
|
})
|
||||||
@ -150,7 +147,7 @@ export function ReplicaSetList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.rs.namespace}
|
||||||
resourceType="replicasets"
|
resourceType="replicasets"
|
||||||
resourceName={activeModal.rs.name}
|
resourceName={activeModal.rs.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ type ActiveModal =
|
|||||||
| { type: "delete"; rq: ResourceQuotaInfo }
|
| { type: "delete"; rq: ResourceQuotaInfo }
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export function ResourceQuotaList({ resourcequotas, clusterId, namespace, onRefresh }: ResourceQuotaListProps) {
|
export function ResourceQuotaList({ resourcequotas, clusterId, namespace: _namespace, onRefresh }: ResourceQuotaListProps) {
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -27,7 +27,7 @@ export function ResourceQuotaList({ resourcequotas, clusterId, namespace, onRefr
|
|||||||
const openEdit = async (rq: ResourceQuotaInfo) => {
|
const openEdit = async (rq: ResourceQuotaInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(clusterId, "resourcequotas", namespace, rq.name);
|
const yaml = await getResourceYamlCmd(clusterId, "resourcequotas", rq.namespace, rq.name);
|
||||||
setActiveModal({ type: "edit", rq, yaml });
|
setActiveModal({ type: "edit", rq, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -38,7 +38,7 @@ export function ResourceQuotaList({ resourcequotas, clusterId, namespace, onRefr
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(clusterId, "resourcequotas", namespace, activeModal.rq.name);
|
await deleteResourceCmd(clusterId, "resourcequotas", activeModal.rq.namespace, activeModal.rq.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -110,7 +110,7 @@ export function ResourceQuotaList({ resourcequotas, clusterId, namespace, onRefr
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.rq.namespace}
|
||||||
resourceType="resourcequotas"
|
resourceType="resourcequotas"
|
||||||
resourceName={activeModal.rq.name}
|
resourceName={activeModal.rq.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -25,12 +25,9 @@ export function RoleBindingList({
|
|||||||
roleBindings,
|
roleBindings,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: RoleBindingListProps) {
|
}: RoleBindingListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -38,7 +35,7 @@ export function RoleBindingList({
|
|||||||
const openEdit = async (rb: RoleBindingInfo) => {
|
const openEdit = async (rb: RoleBindingInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "rolebindings", ns, rb.name);
|
const yaml = await getResourceYamlCmd(cid, "rolebindings", rb.namespace, rb.name);
|
||||||
setActiveModal({ type: "edit", rb, yaml });
|
setActiveModal({ type: "edit", rb, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -49,7 +46,7 @@ export function RoleBindingList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "rolebindings", ns, activeModal.rb.name);
|
await deleteResourceCmd(cid, "rolebindings", activeModal.rb.namespace, activeModal.rb.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -115,7 +112,7 @@ export function RoleBindingList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.rb.namespace}
|
||||||
resourceType="rolebindings"
|
resourceType="rolebindings"
|
||||||
resourceName={activeModal.rb.name}
|
resourceName={activeModal.rb.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -25,12 +25,9 @@ export function RoleList({
|
|||||||
roles,
|
roles,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: RoleListProps) {
|
}: RoleListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -38,7 +35,7 @@ export function RoleList({
|
|||||||
const openEdit = async (role: RoleInfo) => {
|
const openEdit = async (role: RoleInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "roles", ns, role.name);
|
const yaml = await getResourceYamlCmd(cid, "roles", role.namespace, role.name);
|
||||||
setActiveModal({ type: "edit", role, yaml });
|
setActiveModal({ type: "edit", role, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -49,7 +46,7 @@ export function RoleList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "roles", ns, activeModal.role.name);
|
await deleteResourceCmd(cid, "roles", activeModal.role.namespace, activeModal.role.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -113,7 +110,7 @@ export function RoleList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.role.namespace}
|
||||||
resourceType="roles"
|
resourceType="roles"
|
||||||
resourceName={activeModal.role.name}
|
resourceName={activeModal.role.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -25,12 +25,9 @@ export function SecretList({
|
|||||||
secrets,
|
secrets,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: SecretListProps) {
|
}: SecretListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -38,7 +35,7 @@ export function SecretList({
|
|||||||
const openEdit = async (secret: SecretInfo) => {
|
const openEdit = async (secret: SecretInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "secrets", ns, secret.name);
|
const yaml = await getResourceYamlCmd(cid, "secrets", secret.namespace, secret.name);
|
||||||
setActiveModal({ type: "edit", secret, yaml });
|
setActiveModal({ type: "edit", secret, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -49,7 +46,7 @@ export function SecretList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "secrets", ns, activeModal.secret.name);
|
await deleteResourceCmd(cid, "secrets", activeModal.secret.namespace, activeModal.secret.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -117,7 +114,7 @@ export function SecretList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.secret.namespace}
|
||||||
resourceType="secrets"
|
resourceType="secrets"
|
||||||
resourceName={activeModal.secret.name}
|
resourceName={activeModal.secret.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -25,12 +25,9 @@ export function ServiceAccountList({
|
|||||||
serviceAccounts,
|
serviceAccounts,
|
||||||
clusterId,
|
clusterId,
|
||||||
_clusterId,
|
_clusterId,
|
||||||
namespace,
|
|
||||||
_namespace,
|
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}: ServiceAccountListProps) {
|
}: ServiceAccountListProps) {
|
||||||
const cid = clusterId ?? _clusterId ?? "";
|
const cid = clusterId ?? _clusterId ?? "";
|
||||||
const ns = namespace ?? _namespace ?? "";
|
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -38,7 +35,7 @@ export function ServiceAccountList({
|
|||||||
const openEdit = async (sa: ServiceAccountInfo) => {
|
const openEdit = async (sa: ServiceAccountInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(cid, "serviceaccounts", ns, sa.name);
|
const yaml = await getResourceYamlCmd(cid, "serviceaccounts", sa.namespace, sa.name);
|
||||||
setActiveModal({ type: "edit", sa, yaml });
|
setActiveModal({ type: "edit", sa, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -49,7 +46,7 @@ export function ServiceAccountList({
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(cid, "serviceaccounts", ns, activeModal.sa.name);
|
await deleteResourceCmd(cid, "serviceaccounts", activeModal.sa.namespace, activeModal.sa.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -115,7 +112,7 @@ export function ServiceAccountList({
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={cid}
|
clusterId={cid}
|
||||||
namespace={ns}
|
namespace={activeModal.sa.namespace}
|
||||||
resourceType="serviceaccounts"
|
resourceType="serviceaccounts"
|
||||||
resourceName={activeModal.sa.name}
|
resourceName={activeModal.sa.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ type ActiveModal =
|
|||||||
| { type: "delete"; svc: ServiceInfo }
|
| { type: "delete"; svc: ServiceInfo }
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export function ServiceList({ services, clusterId, namespace, onRefresh }: ServiceListProps) {
|
export function ServiceList({ services, clusterId, namespace: _namespace, onRefresh }: ServiceListProps) {
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -43,7 +43,7 @@ export function ServiceList({ services, clusterId, namespace, onRefresh }: Servi
|
|||||||
const openEdit = async (svc: ServiceInfo) => {
|
const openEdit = async (svc: ServiceInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(clusterId, "services", namespace, svc.name);
|
const yaml = await getResourceYamlCmd(clusterId, "services", svc.namespace, svc.name);
|
||||||
setActiveModal({ type: "edit", svc, yaml });
|
setActiveModal({ type: "edit", svc, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -54,7 +54,7 @@ export function ServiceList({ services, clusterId, namespace, onRefresh }: Servi
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(clusterId, "services", namespace, activeModal.svc.name);
|
await deleteResourceCmd(clusterId, "services", activeModal.svc.namespace, activeModal.svc.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -140,7 +140,7 @@ export function ServiceList({ services, clusterId, namespace, onRefresh }: Servi
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.svc.namespace}
|
||||||
resourceType="services"
|
resourceType="services"
|
||||||
resourceName={activeModal.svc.name}
|
resourceName={activeModal.svc.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ type ActiveModal =
|
|||||||
| { type: "delete"; ss: StatefulSetInfo }
|
| { type: "delete"; ss: StatefulSetInfo }
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh }: StatefulSetListProps) {
|
export function StatefulSetList({ statefulsets, clusterId, namespace: _namespace, onRefresh }: StatefulSetListProps) {
|
||||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||||
const [isActing, setIsActing] = useState(false);
|
const [isActing, setIsActing] = useState(false);
|
||||||
const [actionError, setActionError] = useState<string | null>(null);
|
const [actionError, setActionError] = useState<string | null>(null);
|
||||||
@ -35,7 +35,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
|||||||
const openEdit = async (ss: StatefulSetInfo) => {
|
const openEdit = async (ss: StatefulSetInfo) => {
|
||||||
setActionError(null);
|
setActionError(null);
|
||||||
try {
|
try {
|
||||||
const yaml = await getResourceYamlCmd(clusterId, "statefulsets", namespace, ss.name);
|
const yaml = await getResourceYamlCmd(clusterId, "statefulsets", ss.namespace, ss.name);
|
||||||
setActiveModal({ type: "edit", ss, yaml });
|
setActiveModal({ type: "edit", ss, yaml });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setActionError(err instanceof Error ? err.message : String(err));
|
setActionError(err instanceof Error ? err.message : String(err));
|
||||||
@ -46,7 +46,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
|||||||
if (activeModal?.type !== "restart") return;
|
if (activeModal?.type !== "restart") return;
|
||||||
setIsActing(true);
|
setIsActing(true);
|
||||||
try {
|
try {
|
||||||
await restartStatefulsetCmd(clusterId, namespace, activeModal.ss.name);
|
await restartStatefulsetCmd(clusterId, activeModal.ss.namespace, activeModal.ss.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -60,7 +60,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
|||||||
if (activeModal?.type !== "delete") return;
|
if (activeModal?.type !== "delete") return;
|
||||||
setIsActing(true);
|
setIsActing(true);
|
||||||
try {
|
try {
|
||||||
await deleteResourceCmd(clusterId, "statefulsets", namespace, activeModal.ss.name);
|
await deleteResourceCmd(clusterId, "statefulsets", activeModal.ss.namespace, activeModal.ss.name);
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
} finally {
|
} finally {
|
||||||
@ -140,7 +140,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
|||||||
resourceName={activeModal.ss.name}
|
resourceName={activeModal.ss.name}
|
||||||
currentReplicas={activeModal.ss.replicas}
|
currentReplicas={activeModal.ss.replicas}
|
||||||
onScale={(replicas) =>
|
onScale={(replicas) =>
|
||||||
scaleStatefulsetCmd(clusterId, namespace, activeModal.ss.name, replicas).then(() => {
|
scaleStatefulsetCmd(clusterId, activeModal.ss.namespace, activeModal.ss.name, replicas).then(() => {
|
||||||
setActiveModal(null);
|
setActiveModal(null);
|
||||||
onRefresh?.();
|
onRefresh?.();
|
||||||
})
|
})
|
||||||
@ -164,7 +164,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
|||||||
<EditResourceModal
|
<EditResourceModal
|
||||||
isOpen
|
isOpen
|
||||||
clusterId={clusterId}
|
clusterId={clusterId}
|
||||||
namespace={namespace}
|
namespace={activeModal.ss.namespace}
|
||||||
resourceType="statefulsets"
|
resourceType="statefulsets"
|
||||||
resourceName={activeModal.ss.name}
|
resourceName={activeModal.ss.name}
|
||||||
initialYaml={activeModal.yaml}
|
initialYaml={activeModal.yaml}
|
||||||
|
|||||||
@ -135,7 +135,7 @@ export function WorkloadOverview({ resources }: WorkloadOverviewProps) {
|
|||||||
</div>
|
</div>
|
||||||
{pods.length - runningPods - pendingPods - failedPods > 0 && (
|
{pods.length - runningPods - pendingPods - failedPods > 0 && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="inline-block w-3 h-3 rounded-full bg-gray-400" />
|
<span className="inline-block w-3 h-3 rounded-full bg-muted-foreground" />
|
||||||
<span>Other: {pods.length - runningPods - pendingPods - failedPods}</span>
|
<span>Other: {pods.length - runningPods - pendingPods - failedPods}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -795,6 +795,7 @@ export interface PortForwardResponse {
|
|||||||
|
|
||||||
export interface PodInfo {
|
export interface PodInfo {
|
||||||
name: string;
|
name: string;
|
||||||
|
namespace: string;
|
||||||
status: string;
|
status: string;
|
||||||
ready: string;
|
ready: string;
|
||||||
age: string;
|
age: string;
|
||||||
|
|||||||
@ -437,8 +437,10 @@ export function KubernetesPage() {
|
|||||||
const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false);
|
const [isCommandPaletteOpen, setIsCommandPaletteOpen] = useState(false);
|
||||||
const [isPortForwardFormOpen, setIsPortForwardFormOpen] = useState(false);
|
const [isPortForwardFormOpen, setIsPortForwardFormOpen] = useState(false);
|
||||||
const [isNotificationsOpen, setIsNotificationsOpen] = useState(false);
|
const [isNotificationsOpen, setIsNotificationsOpen] = useState(false);
|
||||||
|
const [connectionError, setConnectionError] = useState<string | null>(null);
|
||||||
|
|
||||||
const lastLoadedRef = useRef<{ section: ActiveSection; clusterId: string; namespace: string } | null>(null);
|
const lastLoadedRef = useRef<{ section: ActiveSection; clusterId: string; namespace: string } | null>(null);
|
||||||
|
const initializedRef = useRef(false);
|
||||||
|
|
||||||
// ── Initial data load ──────────────────────────────────────────────────────
|
// ── Initial data load ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
@ -451,12 +453,20 @@ export function KubernetesPage() {
|
|||||||
setKubeconfigs(kubeconfigsData);
|
setKubeconfigs(kubeconfigsData);
|
||||||
setPortForwards(portForwardsData);
|
setPortForwards(portForwardsData);
|
||||||
|
|
||||||
|
if (!initializedRef.current) {
|
||||||
|
initializedRef.current = true;
|
||||||
const activeConfig = kubeconfigsData.find((c) => c.is_active);
|
const activeConfig = kubeconfigsData.find((c) => c.is_active);
|
||||||
if (activeConfig && !selectedClusterId) {
|
const targetId = selectedClusterId ?? activeConfig?.id;
|
||||||
await connectClusterFromKubeconfigCmd(activeConfig.id).catch(() => {});
|
if (targetId) {
|
||||||
setSelectedCluster(activeConfig.id);
|
const err = await connectClusterFromKubeconfigCmd(targetId)
|
||||||
} else if (selectedClusterId) {
|
.then(() => null)
|
||||||
await connectClusterFromKubeconfigCmd(selectedClusterId).catch(() => {});
|
.catch((e: unknown) => e);
|
||||||
|
if (err) {
|
||||||
|
setConnectionError(err instanceof Error ? err.message : String(err));
|
||||||
|
} else {
|
||||||
|
setSelectedCluster(targetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load initial Kubernetes data:", err);
|
console.error("Failed to load initial Kubernetes data:", err);
|
||||||
@ -481,11 +491,7 @@ export function KubernetesPage() {
|
|||||||
|
|
||||||
const loadResourceData = useCallback(
|
const loadResourceData = useCallback(
|
||||||
async (section: ActiveSection, clusterId: string, namespace: string) => {
|
async (section: ActiveSection, clusterId: string, namespace: string) => {
|
||||||
if (
|
if (section === "cluster_overview" || section === "portforwarding") {
|
||||||
section === "cluster_overview" ||
|
|
||||||
section === "portforwarding" ||
|
|
||||||
section === "workloads_overview"
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,6 +500,29 @@ export function KubernetesPage() {
|
|||||||
setIsLoadingResources(true);
|
setIsLoadingResources(true);
|
||||||
try {
|
try {
|
||||||
switch (section) {
|
switch (section) {
|
||||||
|
case "workloads_overview": {
|
||||||
|
const [pods, deployments, statefulsets, daemonsets, jobs, cronjobs] =
|
||||||
|
await Promise.allSettled([
|
||||||
|
listPodsCmd(clusterId, ns),
|
||||||
|
listDeploymentsCmd(clusterId, ns),
|
||||||
|
listStatefulsetsCmd(clusterId, ns),
|
||||||
|
listDaemonsetsCmd(clusterId, ns),
|
||||||
|
listJobsCmd(clusterId, ns),
|
||||||
|
listCronjobsCmd(clusterId, ns),
|
||||||
|
]).then((results) =>
|
||||||
|
results.map((r) => (r.status === "fulfilled" ? r.value : []))
|
||||||
|
);
|
||||||
|
setResources((r) => ({
|
||||||
|
...r,
|
||||||
|
pods: pods as PodInfo[],
|
||||||
|
deployments: deployments as DeploymentInfo[],
|
||||||
|
statefulsets: statefulsets as StatefulSetInfo[],
|
||||||
|
daemonsets: daemonsets as DaemonSetInfo[],
|
||||||
|
jobs: jobs as JobInfo[],
|
||||||
|
cronjobs: cronjobs as CronJobInfo[],
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "pods":
|
case "pods":
|
||||||
await listPodsCmd(clusterId, ns).then((data) =>
|
await listPodsCmd(clusterId, ns).then((data) =>
|
||||||
setResources((r) => ({ ...r, pods: data }))
|
setResources((r) => ({ ...r, pods: data }))
|
||||||
@ -1131,6 +1160,13 @@ export function KubernetesPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{connectionError && (
|
||||||
|
<div className="flex items-center gap-2 px-4 py-2 bg-destructive/10 border-b border-destructive/20 text-destructive text-sm">
|
||||||
|
<span>Cluster connection failed: {connectionError}</span>
|
||||||
|
<button className="ml-auto underline" onClick={() => setConnectionError(null)}>Dismiss</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Main layout: sidebar + content */}
|
{/* Main layout: sidebar + content */}
|
||||||
<div className="flex flex-1 overflow-hidden">
|
<div className="flex flex-1 overflow-hidden">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
|
|||||||
@ -127,6 +127,19 @@ vi.mock("@/components/Kubernetes/Hotbar", () => ({
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/components/Kubernetes/WorkloadOverview", () => ({
|
||||||
|
WorkloadOverview: ({ resources }: { resources: { pods: unknown[]; deployments: unknown[]; statefulsets: unknown[]; daemonsets: unknown[]; jobs: unknown[]; cronjobs: unknown[] } }) => (
|
||||||
|
<div data-testid="workload-overview">
|
||||||
|
<span data-testid="pod-count">{resources.pods.length}</span>
|
||||||
|
<span data-testid="deployment-count">{resources.deployments.length}</span>
|
||||||
|
<span data-testid="statefulset-count">{resources.statefulsets.length}</span>
|
||||||
|
<span data-testid="daemonset-count">{resources.daemonsets.length}</span>
|
||||||
|
<span data-testid="job-count">{resources.jobs.length}</span>
|
||||||
|
<span data-testid="cronjob-count">{resources.cronjobs.length}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
type MockedInvoke = ReturnType<typeof vi.fn>;
|
type MockedInvoke = ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
const mockInvoke = invoke as unknown as MockedInvoke;
|
const mockInvoke = invoke as unknown as MockedInvoke;
|
||||||
@ -504,4 +517,133 @@ describe("KubernetesPage", () => {
|
|||||||
expect(mockInvoke.mock.calls.length).toBeGreaterThanOrEqual(callsBefore);
|
expect(mockInvoke.mock.calls.length).toBeGreaterThanOrEqual(callsBefore);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("WorkloadOverview data loading", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockInvoke.mockImplementation((cmd: string) => {
|
||||||
|
if (cmd === "list_kubeconfigs") return Promise.resolve(MOCK_KUBECONFIGS);
|
||||||
|
if (cmd === "list_namespaces") return Promise.resolve(MOCK_NAMESPACES);
|
||||||
|
if (cmd === "list_port_forwards") return Promise.resolve([]);
|
||||||
|
if (cmd === "list_pods") return Promise.resolve([{ name: "pod-1", namespace: "default", status: "Running", ready: "1/1", restarts: 0, age: "1d", node: "node-1", ip: "10.0.0.1" }]);
|
||||||
|
if (cmd === "list_deployments") return Promise.resolve([{ name: "deploy-1", namespace: "default", ready: "1/1", up_to_date: 1, available: 1, age: "1d" }]);
|
||||||
|
if (cmd === "list_statefulsets") return Promise.resolve([]);
|
||||||
|
if (cmd === "list_daemonsets") return Promise.resolve([]);
|
||||||
|
if (cmd === "list_jobs") return Promise.resolve([]);
|
||||||
|
if (cmd === "list_cronjobs") return Promise.resolve([]);
|
||||||
|
return Promise.resolve([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders WorkloadOverview when workloads_overview section is active", async () => {
|
||||||
|
renderPage();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole("button", { name: "Overview" })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Overview" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("workload-overview")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls list_pods, list_deployments, list_statefulsets, list_daemonsets, list_jobs, list_cronjobs when workloads_overview is active", async () => {
|
||||||
|
renderPage();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole("button", { name: "Overview" })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Overview" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("list_pods", expect.anything());
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("list_deployments", expect.anything());
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("list_statefulsets", expect.anything());
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("list_daemonsets", expect.anything());
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("list_jobs", expect.anything());
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("list_cronjobs", expect.anything());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes fetched resource counts to WorkloadOverview", async () => {
|
||||||
|
renderPage();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole("button", { name: "Overview" })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Overview" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("workload-overview")).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId("pod-count").textContent).toBe("1");
|
||||||
|
expect(screen.getByTestId("deployment-count").textContent).toBe("1");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Connection error banner", () => {
|
||||||
|
it("shows a connection error banner when connectClusterFromKubeconfig fails", async () => {
|
||||||
|
mockInvoke.mockImplementation((cmd: string) => {
|
||||||
|
if (cmd === "list_kubeconfigs") return Promise.resolve(MOCK_KUBECONFIGS);
|
||||||
|
if (cmd === "list_port_forwards") return Promise.resolve([]);
|
||||||
|
if (cmd === "connect_cluster_from_kubeconfig") return Promise.reject(new Error("connection refused"));
|
||||||
|
return Promise.resolve([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPage();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/cluster connection failed/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dismisses the error banner when Dismiss is clicked", async () => {
|
||||||
|
mockInvoke.mockImplementation((cmd: string) => {
|
||||||
|
if (cmd === "list_kubeconfigs") return Promise.resolve(MOCK_KUBECONFIGS);
|
||||||
|
if (cmd === "list_port_forwards") return Promise.resolve([]);
|
||||||
|
if (cmd === "connect_cluster_from_kubeconfig") return Promise.reject(new Error("connection refused"));
|
||||||
|
return Promise.resolve([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPage();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/cluster connection failed/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /dismiss/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(/cluster connection failed/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Double-connect prevention", () => {
|
||||||
|
it("calls connectClusterFromKubeconfig only once on mount even when store updates", async () => {
|
||||||
|
mockInvoke.mockImplementation((cmd: string) => {
|
||||||
|
if (cmd === "list_kubeconfigs") return Promise.resolve(MOCK_KUBECONFIGS);
|
||||||
|
if (cmd === "list_namespaces") return Promise.resolve(MOCK_NAMESPACES);
|
||||||
|
if (cmd === "list_port_forwards") return Promise.resolve([]);
|
||||||
|
return Promise.resolve([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
renderPage();
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(useKubernetesStore.getState().selectedClusterId).toBe("kc-1");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allow any re-renders to settle
|
||||||
|
await waitFor(() => {
|
||||||
|
const connectCalls = mockInvoke.mock.calls.filter(
|
||||||
|
([cmd]) => cmd === "connect_cluster_from_kubeconfig"
|
||||||
|
);
|
||||||
|
expect(connectCalls.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
663
tests/unit/NamespaceActionFix.test.tsx
Normal file
663
tests/unit/NamespaceActionFix.test.tsx
Normal file
@ -0,0 +1,663 @@
|
|||||||
|
/**
|
||||||
|
* TDD tests: action IPC calls in network/config/storage/access-control list
|
||||||
|
* components must use the item's own .namespace, never the filter prop (which
|
||||||
|
* can be "all" when the user is viewing all namespaces).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
import { ServiceList } from "@/components/Kubernetes/ServiceList";
|
||||||
|
import { IngressList } from "@/components/Kubernetes/IngressList";
|
||||||
|
import { ConfigMapList } from "@/components/Kubernetes/ConfigMapList";
|
||||||
|
import { SecretList } from "@/components/Kubernetes/SecretList";
|
||||||
|
import { HPAList } from "@/components/Kubernetes/HPAList";
|
||||||
|
import { PVCList } from "@/components/Kubernetes/PVCList";
|
||||||
|
import { ServiceAccountList } from "@/components/Kubernetes/ServiceAccountList";
|
||||||
|
import { RoleList } from "@/components/Kubernetes/RoleList";
|
||||||
|
import { RoleBindingList } from "@/components/Kubernetes/RoleBindingList";
|
||||||
|
import { NetworkPolicyList } from "@/components/Kubernetes/NetworkPolicyList";
|
||||||
|
import { ResourceQuotaList } from "@/components/Kubernetes/ResourceQuotaList";
|
||||||
|
import { LimitRangeList } from "@/components/Kubernetes/LimitRangeList";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ServiceInfo,
|
||||||
|
IngressInfo,
|
||||||
|
ConfigMapInfo,
|
||||||
|
SecretInfo,
|
||||||
|
HorizontalPodAutoscalerInfo,
|
||||||
|
PersistentVolumeClaimInfo,
|
||||||
|
ServiceAccountInfo,
|
||||||
|
RoleInfo,
|
||||||
|
RoleBindingInfo,
|
||||||
|
NetworkPolicyInfo,
|
||||||
|
ResourceQuotaInfo,
|
||||||
|
LimitRangeInfo,
|
||||||
|
} from "@/lib/tauriCommands";
|
||||||
|
|
||||||
|
type MockedInvoke = typeof invoke & {
|
||||||
|
mockResolvedValue: (v: unknown) => void;
|
||||||
|
mockImplementation: (fn: (cmd: string, args?: unknown) => Promise<unknown>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockInvoke = invoke as MockedInvoke;
|
||||||
|
|
||||||
|
// ─── helpers ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Open the action menu for the first row whose name cell matches `name`. */
|
||||||
|
function openActionMenu(name: string) {
|
||||||
|
const cell = screen.getByText(name);
|
||||||
|
const row = cell.closest("tr")!;
|
||||||
|
const btn = row.querySelector("button")!;
|
||||||
|
fireEvent.click(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Click the first menu item that contains `label`. */
|
||||||
|
function clickMenuItem(label: string) {
|
||||||
|
const item = screen.getByText(label);
|
||||||
|
fireEvent.click(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── ServiceList ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("ServiceList – action IPC uses item.namespace", () => {
|
||||||
|
const svc: ServiceInfo = {
|
||||||
|
name: "my-svc",
|
||||||
|
namespace: "production",
|
||||||
|
type: "ClusterIP",
|
||||||
|
cluster_ip: "10.0.0.1",
|
||||||
|
ports: [],
|
||||||
|
age: "1d",
|
||||||
|
selector: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<ServiceList services={[svc]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("my-svc");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "services",
|
||||||
|
namespace: "production",
|
||||||
|
resourceName: "my-svc",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<ServiceList services={[svc]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("my-svc");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "services",
|
||||||
|
namespace: "production",
|
||||||
|
resourceName: "my-svc",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── IngressList ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("IngressList – action IPC uses item.namespace", () => {
|
||||||
|
const ing: IngressInfo = {
|
||||||
|
name: "my-ingress",
|
||||||
|
namespace: "staging",
|
||||||
|
host: "example.com",
|
||||||
|
addresses: [],
|
||||||
|
age: "2d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<IngressList ingresses={[ing]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("my-ingress");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "ingresses",
|
||||||
|
namespace: "staging",
|
||||||
|
resourceName: "my-ingress",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<IngressList ingresses={[ing]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("my-ingress");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "ingresses",
|
||||||
|
namespace: "staging",
|
||||||
|
resourceName: "my-ingress",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── ConfigMapList ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("ConfigMapList – action IPC uses item.namespace", () => {
|
||||||
|
const cm: ConfigMapInfo = {
|
||||||
|
name: "app-config",
|
||||||
|
namespace: "kube-system",
|
||||||
|
data_keys: 3,
|
||||||
|
age: "10d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<ConfigMapList configmaps={[cm]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("app-config");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "configmaps",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "app-config",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<ConfigMapList configmaps={[cm]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("app-config");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "configmaps",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "app-config",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── SecretList ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("SecretList – action IPC uses item.namespace", () => {
|
||||||
|
const secret: SecretInfo = {
|
||||||
|
name: "db-creds",
|
||||||
|
namespace: "production",
|
||||||
|
type: "Opaque",
|
||||||
|
data_keys: 2,
|
||||||
|
age: "5d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<SecretList secrets={[secret]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("db-creds");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "secrets",
|
||||||
|
namespace: "production",
|
||||||
|
resourceName: "db-creds",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<SecretList secrets={[secret]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("db-creds");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "secrets",
|
||||||
|
namespace: "production",
|
||||||
|
resourceName: "db-creds",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── HPAList ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("HPAList – action IPC uses item.namespace", () => {
|
||||||
|
const hpa: HorizontalPodAutoscalerInfo = {
|
||||||
|
name: "web-hpa",
|
||||||
|
namespace: "default",
|
||||||
|
min_replicas: 1,
|
||||||
|
max_replicas: 10,
|
||||||
|
current_replicas: 3,
|
||||||
|
desired_replicas: 3,
|
||||||
|
age: "7d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<HPAList hpas={[hpa]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("web-hpa");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "horizontalpodautoscalers",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "web-hpa",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<HPAList hpas={[hpa]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("web-hpa");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "horizontalpodautoscalers",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "web-hpa",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── PVCList ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("PVCList – action IPC uses item.namespace", () => {
|
||||||
|
const pvc: PersistentVolumeClaimInfo = {
|
||||||
|
name: "data-pvc",
|
||||||
|
namespace: "staging",
|
||||||
|
status: "Bound",
|
||||||
|
volume: "pv-001",
|
||||||
|
capacity: "10Gi",
|
||||||
|
access_modes: ["ReadWriteOnce"],
|
||||||
|
age: "3d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<PVCList pvcs={[pvc]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("data-pvc");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "persistentvolumeclaims",
|
||||||
|
namespace: "staging",
|
||||||
|
resourceName: "data-pvc",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<PVCList pvcs={[pvc]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("data-pvc");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "persistentvolumeclaims",
|
||||||
|
namespace: "staging",
|
||||||
|
resourceName: "data-pvc",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── ServiceAccountList ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("ServiceAccountList – action IPC uses item.namespace", () => {
|
||||||
|
const sa: ServiceAccountInfo = {
|
||||||
|
name: "app-sa",
|
||||||
|
namespace: "production",
|
||||||
|
secrets: 1,
|
||||||
|
age: "30d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<ServiceAccountList serviceAccounts={[sa]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("app-sa");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "serviceaccounts",
|
||||||
|
namespace: "production",
|
||||||
|
resourceName: "app-sa",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<ServiceAccountList serviceAccounts={[sa]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("app-sa");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "serviceaccounts",
|
||||||
|
namespace: "production",
|
||||||
|
resourceName: "app-sa",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── RoleList ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("RoleList – action IPC uses item.namespace", () => {
|
||||||
|
const role: RoleInfo = {
|
||||||
|
name: "pod-reader",
|
||||||
|
namespace: "default",
|
||||||
|
age: "14d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<RoleList roles={[role]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("pod-reader");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "roles",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "pod-reader",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<RoleList roles={[role]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("pod-reader");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "roles",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "pod-reader",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── RoleBindingList ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("RoleBindingList – action IPC uses item.namespace", () => {
|
||||||
|
const rb: RoleBindingInfo = {
|
||||||
|
name: "pod-reader-binding",
|
||||||
|
namespace: "default",
|
||||||
|
role: "pod-reader",
|
||||||
|
age: "10d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<RoleBindingList roleBindings={[rb]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("pod-reader-binding");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "rolebindings",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "pod-reader-binding",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<RoleBindingList roleBindings={[rb]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("pod-reader-binding");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "rolebindings",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "pod-reader-binding",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── NetworkPolicyList ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("NetworkPolicyList – action IPC uses item.namespace", () => {
|
||||||
|
const np: NetworkPolicyInfo = {
|
||||||
|
name: "deny-all",
|
||||||
|
namespace: "production",
|
||||||
|
pod_selector: "{}",
|
||||||
|
policy_types: ["Ingress"],
|
||||||
|
age: "3d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<NetworkPolicyList networkpolicies={[np]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("deny-all");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "networkpolicies",
|
||||||
|
namespace: "production",
|
||||||
|
resourceName: "deny-all",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<NetworkPolicyList networkpolicies={[np]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("deny-all");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "networkpolicies",
|
||||||
|
namespace: "production",
|
||||||
|
resourceName: "deny-all",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── ResourceQuotaList ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("ResourceQuotaList – action IPC uses item.namespace", () => {
|
||||||
|
const rq: ResourceQuotaInfo = {
|
||||||
|
name: "compute-resources",
|
||||||
|
namespace: "default",
|
||||||
|
request_cpu: "4",
|
||||||
|
request_memory: "8Gi",
|
||||||
|
limit_cpu: "8",
|
||||||
|
limit_memory: "16Gi",
|
||||||
|
age: "7d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<ResourceQuotaList resourcequotas={[rq]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("compute-resources");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "resourcequotas",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "compute-resources",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<ResourceQuotaList resourcequotas={[rq]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("compute-resources");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "resourcequotas",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "compute-resources",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── LimitRangeList ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("LimitRangeList – action IPC uses item.namespace", () => {
|
||||||
|
const lr: LimitRangeInfo = {
|
||||||
|
name: "cpu-mem-limits",
|
||||||
|
namespace: "default",
|
||||||
|
limit_count: 3,
|
||||||
|
age: "14d",
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => vi.clearAllMocks());
|
||||||
|
|
||||||
|
it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue("yaml: content");
|
||||||
|
render(
|
||||||
|
<LimitRangeList limitranges={[lr]} clusterId="c1" namespace="all" />
|
||||||
|
);
|
||||||
|
openActionMenu("cpu-mem-limits");
|
||||||
|
clickMenuItem("Edit");
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "limitranges",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "cpu-mem-limits",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
render(
|
||||||
|
<LimitRangeList limitranges={[lr]} clusterId="c1" namespace="all" onRefresh={() => {}} />
|
||||||
|
);
|
||||||
|
openActionMenu("cpu-mem-limits");
|
||||||
|
clickMenuItem("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "limitranges",
|
||||||
|
namespace: "default",
|
||||||
|
resourceName: "cpu-mem-limits",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -17,6 +17,7 @@ const mockInvoke = invoke as MockedInvoke;
|
|||||||
|
|
||||||
const mockPod: PodInfo = {
|
const mockPod: PodInfo = {
|
||||||
name: "nginx-abc123",
|
name: "nginx-abc123",
|
||||||
|
namespace: "default",
|
||||||
status: "Running",
|
status: "Running",
|
||||||
ready: "2/2",
|
ready: "2/2",
|
||||||
age: "3h",
|
age: "3h",
|
||||||
|
|||||||
223
tests/unit/PodList.test.tsx
Normal file
223
tests/unit/PodList.test.tsx
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { PodList } from "@/components/Kubernetes/PodList";
|
||||||
|
import type { PodInfo } from "@/lib/tauriCommands";
|
||||||
|
|
||||||
|
vi.mock("@tauri-apps/api/core");
|
||||||
|
|
||||||
|
// Silence console.error noise from modal portals in jsdom
|
||||||
|
vi.mock("@/components/Kubernetes/LogsModal", () => ({
|
||||||
|
LogsModal: ({ namespace }: { namespace: string }) => (
|
||||||
|
<div data-testid="logs-modal" data-namespace={namespace} />
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
vi.mock("@/components/Kubernetes/ShellExecModal", () => ({
|
||||||
|
ShellExecModal: ({ namespace }: { namespace: string }) => (
|
||||||
|
<div data-testid="shell-modal" data-namespace={namespace} />
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
vi.mock("@/components/Kubernetes/AttachModal", () => ({
|
||||||
|
AttachModal: ({ namespace }: { namespace: string }) => (
|
||||||
|
<div data-testid="attach-modal" data-namespace={namespace} />
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
vi.mock("@/components/Kubernetes/EditResourceModal", () => ({
|
||||||
|
EditResourceModal: ({ namespace }: { namespace: string }) => (
|
||||||
|
<div data-testid="edit-modal" data-namespace={namespace} />
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
vi.mock("@/components/Kubernetes/ConfirmDeleteDialog", () => ({
|
||||||
|
ConfirmDeleteDialog: ({
|
||||||
|
onConfirm,
|
||||||
|
resourceName,
|
||||||
|
}: {
|
||||||
|
onConfirm: () => void;
|
||||||
|
resourceName: string;
|
||||||
|
}) => (
|
||||||
|
<div data-testid="confirm-delete">
|
||||||
|
<span>{resourceName}</span>
|
||||||
|
<button onClick={onConfirm}>confirm</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
type MockedInvoke = typeof invoke & {
|
||||||
|
mockResolvedValue: (v: unknown) => void;
|
||||||
|
mockRejectedValue: (e: Error) => void;
|
||||||
|
mockImplementation: (fn: (cmd: string) => Promise<unknown>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockInvoke = invoke as MockedInvoke;
|
||||||
|
|
||||||
|
// A pod whose own namespace ("default") differs from the filter prop ("all")
|
||||||
|
const mockPod: PodInfo = {
|
||||||
|
name: "test-pod",
|
||||||
|
namespace: "default",
|
||||||
|
status: "Running",
|
||||||
|
ready: "1/1",
|
||||||
|
age: "1h",
|
||||||
|
containers: ["app"],
|
||||||
|
};
|
||||||
|
|
||||||
|
function openActionMenu() {
|
||||||
|
const trigger = screen.getByRole("button", { name: /actions/i });
|
||||||
|
fireEvent.click(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("PodList — namespace isolation", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Edit action calls getResourceYamlCmd with pod.namespace ("default"), not filter "all"', async () => {
|
||||||
|
mockInvoke.mockImplementation((cmd: string) => {
|
||||||
|
if (cmd === "get_resource_yaml") {
|
||||||
|
return Promise.resolve("apiVersion: v1\nkind: Pod");
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<PodList
|
||||||
|
pods={[mockPod]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
openActionMenu();
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /^edit$/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "default",
|
||||||
|
resourceType: "pods",
|
||||||
|
resourceName: "test-pod",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Delete action calls deleteResourceCmd with pod.namespace ("default"), not filter "all"', async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<PodList
|
||||||
|
pods={[mockPod]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
openActionMenu();
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /^delete$/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("confirm-delete")).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /confirm/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "default",
|
||||||
|
resourceType: "pods",
|
||||||
|
resourceName: "test-pod",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Force Delete action calls forceDeleteResourceCmd with pod.namespace ("default"), not filter "all"', async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
// Force Delete is only visible when pod is Running or Pending
|
||||||
|
render(
|
||||||
|
<PodList
|
||||||
|
pods={[mockPod]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
openActionMenu();
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /^force delete$/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("confirm-delete")).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /confirm/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockInvoke).toHaveBeenCalledWith("force_delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "default",
|
||||||
|
resourceType: "pods",
|
||||||
|
resourceName: "test-pod",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Logs modal receives pod.namespace ("default"), not filter "all"', async () => {
|
||||||
|
render(
|
||||||
|
<PodList
|
||||||
|
pods={[mockPod]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
openActionMenu();
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /^logs$/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const modal = screen.getByTestId("logs-modal");
|
||||||
|
expect(modal.getAttribute("data-namespace")).toBe("default");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Shell modal receives pod.namespace ("default"), not filter "all"', async () => {
|
||||||
|
render(
|
||||||
|
<PodList
|
||||||
|
pods={[mockPod]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
openActionMenu();
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /^shell$/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const modal = screen.getByTestId("shell-modal");
|
||||||
|
expect(modal.getAttribute("data-namespace")).toBe("default");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Attach modal receives pod.namespace ("default"), not filter "all"', async () => {
|
||||||
|
render(
|
||||||
|
<PodList
|
||||||
|
pods={[mockPod]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
openActionMenu();
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /^attach$/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
const modal = screen.getByTestId("attach-modal");
|
||||||
|
expect(modal.getAttribute("data-namespace")).toBe("default");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
695
tests/unit/WorkloadListActions.test.tsx
Normal file
695
tests/unit/WorkloadListActions.test.tsx
Normal file
@ -0,0 +1,695 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { DeploymentList } from "@/components/Kubernetes/DeploymentList";
|
||||||
|
import { StatefulSetList } from "@/components/Kubernetes/StatefulSetList";
|
||||||
|
import { DaemonSetList } from "@/components/Kubernetes/DaemonSetList";
|
||||||
|
import { ReplicaSetList } from "@/components/Kubernetes/ReplicaSetList";
|
||||||
|
import { JobList } from "@/components/Kubernetes/JobList";
|
||||||
|
import { CronJobList } from "@/components/Kubernetes/CronJobList";
|
||||||
|
import type {
|
||||||
|
DeploymentInfo,
|
||||||
|
StatefulSetInfo,
|
||||||
|
DaemonSetInfo,
|
||||||
|
ReplicaSetInfo,
|
||||||
|
JobInfo,
|
||||||
|
CronJobInfo,
|
||||||
|
} from "@/lib/tauriCommands";
|
||||||
|
|
||||||
|
type MockedInvoke = typeof invoke & {
|
||||||
|
mockResolvedValue: (v: unknown) => void;
|
||||||
|
mockImplementation: (fn: (cmd: string) => Promise<unknown>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockInvoke = invoke as MockedInvoke;
|
||||||
|
|
||||||
|
// Helper: open the action menu for the first Actions button, then click a menu item by label
|
||||||
|
async function openMenuAndClick(label: string) {
|
||||||
|
const btn = screen.getAllByRole("button", { name: /actions/i })[0];
|
||||||
|
fireEvent.click(btn);
|
||||||
|
const item = await screen.findByRole("button", { name: new RegExp(label, "i") });
|
||||||
|
fireEvent.click(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── DeploymentList ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("DeploymentList — actions use item.namespace not filter prop", () => {
|
||||||
|
const deployment: DeploymentInfo = {
|
||||||
|
name: "nginx",
|
||||||
|
namespace: "kube-system",
|
||||||
|
ready: "1/1",
|
||||||
|
up_to_date: "1",
|
||||||
|
available: "1",
|
||||||
|
replicas: 1,
|
||||||
|
age: "1d",
|
||||||
|
labels: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||||
|
render(
|
||||||
|
<DeploymentList
|
||||||
|
deployments={[deployment]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Edit");
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "deployments",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "nginx",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "delete_resource") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DeploymentList
|
||||||
|
deployments={[deployment]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "deployments",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "nginx",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ScaleModal onScale calls scaleDeploymentCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DeploymentList
|
||||||
|
deployments={[deployment]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Scale");
|
||||||
|
const scaleBtn = await screen.findByRole("button", { name: /scale/i });
|
||||||
|
fireEvent.click(scaleBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("scale_deployment", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
deploymentName: "nginx",
|
||||||
|
replicas: expect.any(Number),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleRestart calls restartDeploymentCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "restart_deployment") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DeploymentList
|
||||||
|
deployments={[deployment]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Restart");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|restart/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("restart_deployment", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
deploymentName: "nginx",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleRollback calls rollbackDeploymentCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "rollback_deployment") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DeploymentList
|
||||||
|
deployments={[deployment]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Rollback");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|rollback/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("rollback_deployment", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
deploymentName: "nginx",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── StatefulSetList ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("StatefulSetList — actions use item.namespace not filter prop", () => {
|
||||||
|
const ss: StatefulSetInfo = {
|
||||||
|
name: "postgres",
|
||||||
|
namespace: "kube-system",
|
||||||
|
ready: "1/1",
|
||||||
|
replicas: 1,
|
||||||
|
age: "2d",
|
||||||
|
labels: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||||
|
render(
|
||||||
|
<StatefulSetList
|
||||||
|
statefulsets={[ss]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Edit");
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "statefulsets",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "postgres",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "delete_resource") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<StatefulSetList
|
||||||
|
statefulsets={[ss]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "statefulsets",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "postgres",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ScaleModal onScale calls scaleStatefulsetCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<StatefulSetList
|
||||||
|
statefulsets={[ss]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Scale");
|
||||||
|
const scaleBtn = await screen.findByRole("button", { name: /scale/i });
|
||||||
|
fireEvent.click(scaleBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("scale_statefulset", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
name: "postgres",
|
||||||
|
replicas: expect.any(Number),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleRestart calls restartStatefulsetCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "restart_statefulset") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<StatefulSetList
|
||||||
|
statefulsets={[ss]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Restart");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|restart/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("restart_statefulset", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
name: "postgres",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── DaemonSetList ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("DaemonSetList — actions use item.namespace not filter prop", () => {
|
||||||
|
const ds: DaemonSetInfo = {
|
||||||
|
name: "fluentd",
|
||||||
|
namespace: "kube-system",
|
||||||
|
desired: 3,
|
||||||
|
current: 3,
|
||||||
|
ready: 3,
|
||||||
|
up_to_date: 3,
|
||||||
|
available: 3,
|
||||||
|
age: "5d",
|
||||||
|
labels: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||||
|
render(
|
||||||
|
<DaemonSetList
|
||||||
|
daemonsets={[ds]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Edit");
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "daemonsets",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "fluentd",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "delete_resource") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DaemonSetList
|
||||||
|
daemonsets={[ds]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "daemonsets",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "fluentd",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleRestart calls restartDaemonsetCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "restart_daemonset") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<DaemonSetList
|
||||||
|
daemonsets={[ds]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Restart");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|restart/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("restart_daemonset", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
name: "fluentd",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── ReplicaSetList ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("ReplicaSetList — actions use item.namespace not filter prop", () => {
|
||||||
|
const rs: ReplicaSetInfo = {
|
||||||
|
name: "nginx-abc12",
|
||||||
|
namespace: "kube-system",
|
||||||
|
replicas: 2,
|
||||||
|
ready: "2",
|
||||||
|
age: "3d",
|
||||||
|
labels: { app: "nginx" },
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||||
|
render(
|
||||||
|
<ReplicaSetList
|
||||||
|
replicaSets={[rs]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Edit");
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "replicasets",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "nginx-abc12",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "delete_resource") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ReplicaSetList
|
||||||
|
replicaSets={[rs]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "replicasets",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "nginx-abc12",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ScaleModal onScale calls scaleReplicasetCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
render(
|
||||||
|
<ReplicaSetList
|
||||||
|
replicaSets={[rs]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Scale");
|
||||||
|
const scaleBtn = await screen.findByRole("button", { name: /scale/i });
|
||||||
|
fireEvent.click(scaleBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("scale_replicaset", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
name: "nginx-abc12",
|
||||||
|
replicas: expect.any(Number),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── JobList ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("JobList — actions use item.namespace not filter prop", () => {
|
||||||
|
const job: JobInfo = {
|
||||||
|
name: "db-migrate",
|
||||||
|
namespace: "kube-system",
|
||||||
|
completions: "1/1",
|
||||||
|
duration: "45s",
|
||||||
|
age: "1d",
|
||||||
|
labels: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockInvoke.mockResolvedValue("apiVersion: batch/v1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||||
|
render(
|
||||||
|
<JobList
|
||||||
|
jobs={[job]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Edit");
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "jobs",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "db-migrate",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "delete_resource") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<JobList
|
||||||
|
jobs={[job]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "jobs",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "db-migrate",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── CronJobList ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("CronJobList — actions use item.namespace not filter prop", () => {
|
||||||
|
const cj: CronJobInfo = {
|
||||||
|
name: "backup",
|
||||||
|
namespace: "kube-system",
|
||||||
|
schedule: "0 2 * * *",
|
||||||
|
active: 0,
|
||||||
|
last_schedule: "1h",
|
||||||
|
age: "10d",
|
||||||
|
labels: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockInvoke.mockResolvedValue("apiVersion: batch/v1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||||
|
render(
|
||||||
|
<CronJobList
|
||||||
|
cronJobs={[cj]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Edit");
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "cronjobs",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "backup",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "delete_resource") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CronJobList
|
||||||
|
cronJobs={[cj]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Delete");
|
||||||
|
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||||
|
fireEvent.click(confirmBtn);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||||
|
clusterId: "c1",
|
||||||
|
resourceType: "cronjobs",
|
||||||
|
namespace: "kube-system",
|
||||||
|
resourceName: "backup",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleSuspend calls suspendCronjobCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "suspend_cronjob") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CronJobList
|
||||||
|
cronJobs={[cj]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Suspend");
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("suspend_cronjob", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
name: "backup",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handleTrigger calls triggerCronjobCmd with item.namespace, not 'all'", async () => {
|
||||||
|
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||||
|
if (cmd === "trigger_cronjob") return undefined;
|
||||||
|
return "yaml";
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<CronJobList
|
||||||
|
cronJobs={[cj]}
|
||||||
|
clusterId="c1"
|
||||||
|
namespace="all"
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await openMenuAndClick("Trigger");
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(invoke).toHaveBeenCalledWith("trigger_cronjob", {
|
||||||
|
clusterId: "c1",
|
||||||
|
namespace: "kube-system",
|
||||||
|
name: "backup",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user