From e68f61461ee5f77cf6121831739181136e009696 Mon Sep 17 00:00:00 2001 From: Shaun Arman Date: Mon, 8 Jun 2026 20:15:19 -0500 Subject: [PATCH] fix(ci): cargo fmt kube.rs + switch pr-review to qwen3-coder-next - Apply cargo fmt to src-tauri/src/commands/kube.rs (CI was failing) - Update pr-review.yml to use qwen3-coder-next model via liteLLM - Add TICKET-kube-ui-feature-parity.md gap analysis for FreeLens parity Co-Authored-By: TFTSR Engineering --- .gitea/workflows/pr-review.yml | 4 +- TICKET-kube-ui-feature-parity.md | 280 +++++++++++++++++++++++++++++++ src-tauri/src/commands/kube.rs | 39 +++-- 3 files changed, 311 insertions(+), 12 deletions(-) create mode 100644 TICKET-kube-ui-feature-parity.md diff --git a/.gitea/workflows/pr-review.yml b/.gitea/workflows/pr-review.yml index f815a0bf..4cacd77f 100644 --- a/.gitea/workflows/pr-review.yml +++ b/.gitea/workflows/pr-review.yml @@ -242,7 +242,7 @@ jobs: # Write body to file — passing 100KB+ JSON as a shell arg hits ARG_MAX. jq -cn \ - --arg model "qwen36-35b-a3b-nvfp4" \ + --arg model "qwen3-coder-next" \ --rawfile content /tmp/prompt.txt \ '{model: $model, messages: [{role: "user", content: $content}], stream: false}' \ > /tmp/body.json @@ -359,7 +359,7 @@ jobs: if [ -f "/tmp/pr_review.txt" ] && [ -s "/tmp/pr_review.txt" ]; then REVIEW_BODY=$(head -c 65536 /tmp/pr_review.txt) BODY=$(jq -n \ - --arg body "Automated PR Review (qwen36-35b-a3b-nvfp4 via liteLLM):\n\n${REVIEW_BODY}" \ + --arg body "Automated PR Review (qwen3-coder-next via liteLLM):\n\n${REVIEW_BODY}" \ '{body: $body, event: "COMMENT"}') else BODY=$(jq -n \ diff --git a/TICKET-kube-ui-feature-parity.md b/TICKET-kube-ui-feature-parity.md new file mode 100644 index 00000000..3511acff --- /dev/null +++ b/TICKET-kube-ui-feature-parity.md @@ -0,0 +1,280 @@ +# TICKET: Kubernetes UI — FreeLens v5 Feature Parity + +## Description + +Full gap analysis and implementation plan to bring the TFTSR Kubernetes Management UI to +feature parity with Lens Desktop v5 / FreeLens (MIT-licensed, https://github.com/freelensapp/freelens). + +Analysis confirmed the following areas require work: + +1. **Navigation structure** does not match the requested layout — wrong grouping, missing top-level + sections (Namespaces, Helm, Custom Resources), and missing items within existing sections. +2. **Resource actions** are incomplete across all resource types — pods, deployments, stateful sets, + daemon sets, config maps, secrets, services, nodes, and all others are missing Edit, Delete, and + resource-specific actions (Shell, Attach, Force Delete, Scale, Restart, etc.). +3. **Missing resource types** — 16+ resource types have no backend command, no list view, and no nav entry. +4. **Log streaming** is a static one-shot fetch; FreeLens streams with follow, timestamps, search, and download. +5. **Helm integration** is entirely absent — no Charts browser, no Releases management. +6. **Custom Resources / CRDs** are entirely absent. +7. **PR review workflow** was using stale model `qwen36-35b-a3b-nvfp4`; updated to `qwen3-coder-next`. +8. **`cargo fmt` CI failure** on `kube.rs` — fixed. + +MIT-license compliance: FreeLens is MIT. All feature parity work is independent implementation using +`kubectl` CLI calls matching public Kubernetes API semantics. No FreeLens source is copied. + +--- + +## Acceptance Criteria + +### Navigation + +- [ ] Nav matches the requested layout exactly: + ``` + Cluster + Nodes + Workloads + Overview + Pods + Deployments + Daemon Sets + Stateful Sets + Replica Sets + Replication Controllers + Jobs + Cron Jobs + Config + Config Maps + Secrets + Resource Quotas + Limit Ranges + Horizontal Pod Autoscalers + Pod Disruption Budgets + Priority Classes + Runtime Classes + Leases + Mutating Webhook Configs + Validating Webhook Configs + Network + Services + Endpoint Slices + Endpoints + Ingresses + Ingress Classes + Network Policies + Port Forwarding + Storage + Persistent Volume Claims + Persistent Volumes + Storage Classes + Namespaces + Events + Helm + Charts + Resources + Access Control + Service Accounts + Cluster Roles + Roles + Cluster Role Bindings + Role Bindings + Custom Resources + Definitions + ``` + +### Resource Actions (all resource types) + +- [ ] **Pods**: Logs (streaming with follow/timestamps/search), Shell (exec -it, container selector), + Attach, Edit (YAML), Delete (with confirmation), Force Delete (state-aware: only Running/Pending) +- [ ] **Deployments**: Scale, Rolling Restart, Rollback, Edit (YAML), Delete +- [ ] **StatefulSets**: Scale, Rolling Restart, Edit (YAML), Delete +- [ ] **DaemonSets**: Rolling Restart, Edit (YAML), Delete +- [ ] **ReplicaSets**: Scale, Edit (YAML), Delete +- [ ] **Replication Controllers**: Scale, Edit (YAML), Delete +- [ ] **Jobs**: Delete +- [ ] **CronJobs**: Suspend, Resume, Trigger Now, Edit (YAML), Delete +- [ ] **Services**: Edit (YAML), Delete, Port Forward shortcut +- [ ] **Ingresses**: Edit (YAML), Delete +- [ ] **ConfigMaps**: View data (key/value display), Edit (YAML), Delete +- [ ] **Secrets**: Reveal values (decode base64), Edit (YAML), Delete +- [ ] **HPAs**: Edit (YAML), Delete +- [ ] **PVCs**: Edit (YAML), Delete +- [ ] **PVs**: Edit (YAML), Delete +- [ ] **Storage Classes**: Edit (YAML), Delete +- [ ] **Resource Quotas**: Edit (YAML), Delete +- [ ] **Limit Ranges**: Edit (YAML), Delete +- [ ] **Nodes**: Cordon, Uncordon, Drain, Shell (exec), Describe +- [ ] **Service Accounts / Roles / ClusterRoles / Bindings**: Edit (YAML), Delete +- [ ] **Namespaces**: Create, Delete (with confirmation) +- [ ] **Network Policies**: Edit (YAML), Delete + +### New Resource Types (backend + list view + nav) + +- [ ] **Replication Controllers** (`kubectl get replicationcontrollers`) +- [ ] **Pod Disruption Budgets** (`kubectl get poddisruptionbudgets`) +- [ ] **Priority Classes** (`kubectl get priorityclasses`) +- [ ] **Runtime Classes** (`kubectl get runtimeclasses`) +- [ ] **Leases** (`kubectl get leases`) +- [ ] **Mutating Webhook Configurations** (`kubectl get mutatingwebhookconfigurations`) +- [ ] **Validating Webhook Configurations** (`kubectl get validatingwebhookconfigurations`) +- [ ] **Endpoints** (`kubectl get endpoints`) +- [ ] **Endpoint Slices** (`kubectl get endpointslices`) +- [ ] **Ingress Classes** (`kubectl get ingressclasses`) +- [ ] **Namespaces** (as a browsable list, not just a filter) +- [ ] **Helm Charts** (`helm search repo` / `helm repo` management) +- [ ] **Helm Releases** (`helm list` across namespaces, upgrade, rollback, uninstall) +- [ ] **CRD Definitions** (`kubectl get crds`) + +### Functional Improvements + +- [ ] Log streaming: follow mode, timestamps toggle, search/filter, download +- [ ] All destructive actions require a confirmation dialog showing resource name +- [ ] Force delete is only offered for pods in Running/Pending phase (state-aware context menu) +- [ ] Resource detail drawer: structured metadata, conditions, events, containers, YAML tab +- [ ] Edit Resource modal uses YAML editor with syntax highlighting and validation +- [ ] Shell/exec: auto-detects available shell (bash → ash → sh), container selector for multi-container pods +- [ ] Port Forwarding moved to Network section, "Open in Browser" button for HTTP ports + +### CI / Workflow + +- [ ] `cargo fmt` CI check passes +- [ ] PR review uses `qwen3-coder-next` model + +--- + +## Work Implemented + +### Phase 0 — Already done on this branch + +| Item | Status | +|------|--------| +| `cargo fmt` failure on `kube.rs` | ✅ Fixed | +| PR review model → `qwen3-coder-next` | ✅ Updated | + +### Phase 1 — Navigation Restructure + +**Files**: `src/pages/Kubernetes/KubernetesPage.tsx` + +- Reorder `NAV_SECTIONS` to match the requested layout exactly +- Add top-level sections: Namespaces, Events, Helm, Custom Resources +- Move Port Forwarding from Cluster → Network +- Move Overview from Cluster → Workloads +- Add missing `ActiveSection` union values +- Add routing for all new sections + +### Phase 2 — Missing Resource Backends (Rust) + +**File**: `src-tauri/src/commands/kube.rs` +**New Tauri commands** (all follow existing `list_*` pattern with `--output json`): + +| Command | Resource | +|---------|----------| +| `list_replicationcontrollers` | Replication Controllers | +| `list_poddisruptionbudgets` | Pod Disruption Budgets | +| `list_priorityclasses` | Priority Classes | +| `list_runtimeclasses` | Runtime Classes | +| `list_leases` | Leases | +| `list_mutatingwebhookconfigurations` | Mutating Webhooks | +| `list_validatingwebhookconfigurations` | Validating Webhooks | +| `list_endpoints` | Endpoints | +| `list_endpointslices` | Endpoint Slices | +| `list_ingressclasses` | Ingress Classes | +| `attach_pod` | Pod attach (`kubectl attach -it`) | +| `force_delete_resource` | Force delete (`--grace-period=0 --force`) | +| `helm_list_repos` | Helm repo list | +| `helm_search_repo` | Helm chart search | +| `helm_list_releases` | Helm release list | +| `helm_upgrade` | Helm upgrade/install | +| `helm_rollback` | Helm rollback | +| `helm_uninstall` | Helm release delete | +| `list_crds` | CRD definitions | +| `list_custom_resources` | CRD instances by group/version/resource | +| `list_namespaces_resource` | Namespaces as a resource list (with status/age) | +| `create_namespace` | Create namespace | +| `delete_namespace` | Delete namespace | +| `get_resource_yaml` | Fetch any resource as YAML for editor | +| `describe_resource` | `kubectl describe` output | +| `stream_pod_logs` | Streaming logs (SSE or Tauri event channel) | +| `restart_statefulset` | `kubectl rollout restart sts/` | +| `restart_daemonset` | `kubectl rollout restart ds/` | +| `scale_statefulset` | `kubectl scale sts/` | +| `scale_replicaset` | `kubectl scale rs/` | +| `suspend_cronjob` | Patch CronJob spec.suspend=true | +| `resume_cronjob` | Patch CronJob spec.suspend=false | +| `trigger_cronjob` | `kubectl create job --from=cronjob/` | + +### Phase 3 — Missing Resource List Components (React) + +**Directory**: `src/components/Kubernetes/` +New components needed: + +| Component | Notes | +|-----------|-------| +| `ReplicationControllerList.tsx` | | +| `PodDisruptionBudgetList.tsx` | | +| `PriorityClassList.tsx` | | +| `RuntimeClassList.tsx` | | +| `LeaseList.tsx` | | +| `MutatingWebhookList.tsx` | | +| `ValidatingWebhookList.tsx` | | +| `EndpointList.tsx` | | +| `EndpointSliceList.tsx` | | +| `IngressClassList.tsx` | | +| `NamespaceList.tsx` | With Create/Delete actions | +| `HelmChartList.tsx` | Charts browser | +| `HelmReleaseList.tsx` | Releases with Upgrade/Rollback/Uninstall | +| `CrdList.tsx` | CRD definitions | +| `WorkloadOverview.tsx` | Summary dashboard for Workloads section | + +### Phase 4 — Resource Action Context Menus + +**Pattern**: Each list component gets a `ResourceActionMenu` dropdown with state-aware items. + +Common shared component: `ResourceActionMenu.tsx` accepting: +```ts +interface ResourceAction { + label: string; + icon: React.ElementType; + onClick: () => void; + variant?: "default" | "destructive"; + disabled?: boolean; + hidden?: boolean; +} +``` + +Pod-specific: shell (with container selector), attach, logs, edit, delete, force delete (only shown +when pod.status ∈ {Running, Pending}). + +All destructive actions (delete, force delete, drain, uninstall) open a `ConfirmDeleteDialog.tsx` +displaying the resource name before proceeding. + +### Phase 5 — Log Streaming + +Replace static `getPodLogsCmd` with streaming using Tauri event channel: +- Backend: `stream_pod_logs` spawns `kubectl logs --follow` and emits Tauri events per line +- Frontend: `LogStreamPanel.tsx` — virtual-scrolled, follow toggle, timestamps toggle, search, download + +### Phase 6 — YAML Editor Integration + +`EditResourceModal.tsx` exists. Wire it to all resource types via `get_resource_yaml` + `edit_resource`. +Add read-only YAML tab to all detail views. + +--- + +## Testing Needed + +- [ ] `cargo test --manifest-path src-tauri/Cargo.toml` — all existing tests pass after new commands added +- [ ] Each new `list_*` Rust command has a unit test with mock JSON fixture +- [ ] `attach_pod` and `force_delete_resource` have unit tests validating command construction +- [ ] `npx tsc --noEmit` — zero TypeScript errors +- [ ] `npx eslint . --max-warnings 0` — zero lint warnings +- [ ] `cargo fmt --check` — clean +- [ ] `cargo clippy -- -D warnings` — zero warnings +- [ ] Manual: all 14+ new nav items render without errors against a live cluster +- [ ] Manual: Pod action menu shows all 6 actions; Force Delete hidden for Succeeded/Failed pods +- [ ] Manual: Delete confirmation dialog shows resource name and requires confirmation +- [ ] Manual: Log streaming follows new output in real time, search highlights matches +- [ ] Manual: YAML editor loads existing resource YAML and successfully applies edits +- [ ] Manual: Helm Charts list shows available charts; Releases list shows installed releases +- [ ] Manual: CRD list shows definitions; clicking a CRD shows its instances +- [ ] CI: `cargo fmt --check` passes (was failing before this branch) +- [ ] CI: PR review workflow uses `qwen3-coder-next` model diff --git a/src-tauri/src/commands/kube.rs b/src-tauri/src/commands/kube.rs index 334dbaba..534a0d63 100644 --- a/src-tauri/src/commands/kube.rs +++ b/src-tauri/src/commands/kube.rs @@ -238,9 +238,9 @@ fn detect_auth_method(kubeconfig: &str, context_name: &str) -> String { .get("contexts") .and_then(|c| c.as_sequence()) .and_then(|contexts| { - contexts.iter().find(|ctx| { - ctx.get("name").and_then(|n| n.as_str()) == Some(context_name) - }) + contexts + .iter() + .find(|ctx| ctx.get("name").and_then(|n| n.as_str()) == Some(context_name)) }) .and_then(|ctx| ctx.get("context")) .and_then(|c| c.get("user")) @@ -252,9 +252,9 @@ fn detect_auth_method(kubeconfig: &str, context_name: &str) -> String { .get("users") .and_then(|u| u.as_sequence()) .and_then(|users| { - users.iter().find(|u| { - u.get("name").and_then(|n| n.as_str()) == Some(user_name.as_str()) - }) + users + .iter() + .find(|u| u.get("name").and_then(|n| n.as_str()) == Some(user_name.as_str())) }) .and_then(|u| u.get("user")); @@ -341,9 +341,20 @@ pub async fn test_kubectl_connection( let healthz_body = String::from_utf8_lossy(&healthz.stdout).trim().to_string(); let healthz_err = String::from_utf8_lossy(&healthz.stderr).trim().to_string(); let connectivity_line = if healthz_ok { - format!("OK ({})", if healthz_body.is_empty() { "cluster reachable" } else { &healthz_body }) + format!( + "OK ({})", + if healthz_body.is_empty() { + "cluster reachable" + } else { + &healthz_body + } + ) } else { - let hint = if healthz_err.is_empty() { "no stderr" } else { healthz_err.lines().last().unwrap_or(&healthz_err) }; + let hint = if healthz_err.is_empty() { + "no stderr" + } else { + healthz_err.lines().last().unwrap_or(&healthz_err) + }; format!("FAIL — {hint}") }; @@ -372,8 +383,16 @@ pub async fn test_kubectl_connection( auth = auth_method, connectivity = connectivity_line, exit = exit_code, - stdout = if stdout.is_empty() { "(none)\n" } else { &stdout }, - stderr = if stderr.is_empty() { "(none)\n" } else { &stderr }, + stdout = if stdout.is_empty() { + "(none)\n" + } else { + &stdout + }, + stderr = if stderr.is_empty() { + "(none)\n" + } else { + &stderr + }, )) }