feat: implement full Lens-like Kubernetes UI with resource discovery and management #75
@ -1,6 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
target/
|
|
||||||
src-tauri/target/
|
|
||||||
coverage/
|
|
||||||
tailwind.config.ts
|
|
||||||
99
TICKET-kube-pr-review-fixes.md
Normal file
99
TICKET-kube-pr-review-fixes.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# Kubernetes UI PR Review Fixes
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Resolved all findings from the automated PR review (qwen3-coder-next) of the Kubernetes resource discovery and management feature. The review identified two blockers and several warnings across Rust backend and React frontend.
|
||||||
|
|
||||||
|
**Root cause of blockers:** All six JSON parsing functions in `kube.rs` imported and used `serde_yaml::Value` / `serde_yaml::from_str` against kubectl's JSON output (`-o json`), causing parse failures or incorrect data at runtime. YAML is a superset of JSON and sometimes parses silently incorrectly; the correct parser is `serde_json`.
|
||||||
|
|
||||||
|
**Secondary issues:** `PodInfo` lacked container name data, so the log viewer could only show the pod name as the container selector. The `exec_pod` command had an incorrect kubectl argument order (container `-c` flag placed after `--`, so it was passed to the shell inside the pod rather than to kubectl). The "All Namespaces" filter passed an empty string to kubectl `-n ""` which is invalid.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [x] All six `parse_*_json` functions use `serde_json::from_str` and `serde_json::Value` API (`as_array`, `as_object`)
|
||||||
|
- [x] `PodInfo` struct carries `containers: Vec<String>`; container names parsed from `spec.containers[*].name`
|
||||||
|
- [x] `PodList.tsx` container selector populates from `selectedPod.containers`
|
||||||
|
- [x] `exec_pod` container `-c` flag is placed before `--` separator (correct kubectl syntax)
|
||||||
|
- [x] `exec_pod` accepts optional `shell` parameter with allowlist validation (`sh`, `bash`, `ash`, `dash`)
|
||||||
|
- [x] Empty namespace string routes to `--all-namespaces` in all five list commands
|
||||||
|
- [x] Dialog inner div uses `overflow-y-auto` to handle content overflow on small screens
|
||||||
|
- [x] `getNamespaceOptions` memoized with `useMemo`
|
||||||
|
- [x] `eslint.config.js` deduplicated (was 272 lines, duplicate blocks removed), global ignore fixed
|
||||||
|
- [x] Unused imports removed from all Kubernetes list components
|
||||||
|
- [x] `cargo clippy -- -D warnings`: zero warnings
|
||||||
|
- [x] `tsc --noEmit`: zero errors
|
||||||
|
- [x] `eslint . --max-warnings 0`: zero warnings
|
||||||
|
- [x] 331 Rust tests passing, 98 frontend tests passing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Work Implemented
|
||||||
|
|
||||||
|
### `src-tauri/src/commands/kube.rs`
|
||||||
|
- Replaced `use serde_yaml::Value` with `use serde_json::Value`
|
||||||
|
- `extract_context` and `extract_server_url`: explicitly typed as `serde_yaml::Value` (these legitimately parse YAML kubeconfig files)
|
||||||
|
- `PodInfo` struct: added `containers: Vec<String>` field
|
||||||
|
- `parse_pods_json`: switched to `serde_json::from_str`, `as_array()`; added container name extraction from `spec.containers[].name`
|
||||||
|
- `parse_namespaces_json`, `parse_services_json`, `parse_deployments_json`, `parse_statefulsets_json`, `parse_daemonsets_json`: switched to `serde_json::from_str`, `as_array()`, `as_object()`; updated mapping iterators (serde_json object keys are `String`, not `Value`)
|
||||||
|
- `parse_services_json`: fixed `.as_sequence()` → `.as_array()` in `external_ip` ingress chain
|
||||||
|
- `list_pods`, `list_services`, `list_deployments`, `list_statefulsets`, `list_daemonsets`: handle empty `namespace` with `--all-namespaces`
|
||||||
|
- `exec_pod`: added optional `shell: Option<String>` parameter; allowlist validates against `["sh","bash","ash","dash","/bin/sh","/bin/bash","/bin/ash","/bin/dash"]`; fixed argument order so `-c container` appears before `--`
|
||||||
|
- Phase 3 stub commands: added `#[allow(unused_variables)]` to suppress Clippy warnings on unimplemented stubs
|
||||||
|
|
||||||
|
### `src/lib/tauriCommands.ts`
|
||||||
|
- `PodInfo` interface: added `containers: string[]`
|
||||||
|
- `execPodCmd`: added optional `shell?: string` parameter, passed through to IPC
|
||||||
|
|
||||||
|
### `src/components/Kubernetes/PodList.tsx`
|
||||||
|
- Fixed: `const containers = selectedPod ? [selectedPod.name] : []` → `selectedPod?.containers ?? []`
|
||||||
|
- Fixed: `overflow-hidden` → `overflow-y-auto` on inner dialog content div
|
||||||
|
- Removed unused imports: `Card`, `CardContent`, `CardHeader`, `CardTitle`
|
||||||
|
|
||||||
|
### `src/components/Kubernetes/ResourceBrowser.tsx`
|
||||||
|
- Added `useCallback` import; wrapped `loadData` in `useCallback([clusterId, selectedNamespace])`
|
||||||
|
- `useEffect` deps updated to `[loadData, resourceType]`
|
||||||
|
- Removed unused `CardTitle` import
|
||||||
|
- `getNamespaceOptions` converted to memoized `namespaceOptions` via `useMemo`
|
||||||
|
|
||||||
|
### `src/components/Kubernetes/DaemonSetList.tsx`, `ServiceList.tsx`, `StatefulSetList.tsx`
|
||||||
|
- Removed unused `Card`, `CardContent`, `CardHeader`, `CardTitle` imports
|
||||||
|
- Renamed unused props: `clusterId: _clusterId`, `namespace: _namespace`
|
||||||
|
|
||||||
|
### `src/components/Kubernetes/DeploymentList.tsx`
|
||||||
|
- Removed unused `Card`, `CardContent`, `CardHeader`, `CardTitle` imports
|
||||||
|
|
||||||
|
### `src/components/ui/index.tsx`
|
||||||
|
- `TableRow`: renamed unused `hover` prop to `_hover`
|
||||||
|
|
||||||
|
### `src/App.tsx`
|
||||||
|
- Removed two debug `console.log` calls (auto-testing provider connection)
|
||||||
|
|
||||||
|
### `src/pages/Triage/index.tsx`
|
||||||
|
- `useEffect`: added `addMessage`, `setActiveDomain`, `startSession` to dependency array (stable Zustand store actions)
|
||||||
|
|
||||||
|
### `src/pages/LogUpload/index.tsx`
|
||||||
|
- `handleImagesUpload`: wrapped in `useCallback([id])` and moved before `handleImageDrop` to resolve declaration-order issue
|
||||||
|
- `handleImageDrop`: updated deps from `[id]` to `[handleImagesUpload]`
|
||||||
|
|
||||||
|
### `eslint.config.js`
|
||||||
|
- Removed duplicate config block (file was doubled to 272 lines)
|
||||||
|
- Fixed global ignore: moved `ignores` array to a standalone config object (was incorrectly paired with `files`)
|
||||||
|
- CLI section: added `"log"` to allowed console methods (CLI tool output)
|
||||||
|
|
||||||
|
### `.eslintignore`
|
||||||
|
- Deleted — content migrated to `eslint.config.js` global ignore
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Needed
|
||||||
|
|
||||||
|
- [ ] Connect a real kubeconfig and verify pod/namespace/service/deployment/statefulset/daemonset lists render correctly with JSON from kubectl
|
||||||
|
- [ ] Select "All Namespaces" — verify `--all-namespaces` is used and resources from all namespaces appear
|
||||||
|
- [ ] Open pod log dialog — verify container dropdown shows actual container names (not pod name)
|
||||||
|
- [ ] Fetch logs for a multi-container pod — verify correct container logs are returned
|
||||||
|
- [ ] Test `exec_pod` via UI with `sh` (default) and `bash` — verify both work
|
||||||
|
- [ ] Test `exec_pod` with an invalid shell name (e.g., `zsh`) — verify it returns an error
|
||||||
|
- [ ] Verify "All Namespaces" view does not trigger empty-namespace kubectl error
|
||||||
|
- [ ] Smoke test triage and log upload flows to verify `useEffect`/`useCallback` hook changes have no regressions
|
||||||
760
docs/KUBERNETES-MANAGEMENT-IMPLEMENTATION-PLAN.md
Normal file
760
docs/KUBERNETES-MANAGEMENT-IMPLEMENTATION-PLAN.md
Normal file
@ -0,0 +1,760 @@
|
|||||||
|
# Kubernetes Management UI - Complete Feature Implementation Plan
|
||||||
|
|
||||||
|
## Project: tftsr-devops_investigation v1.1.0
|
||||||
|
## Target: 100% Lens Desktop v5.x Feature Parity (MIT Licensed)
|
||||||
|
## Architecture: Tauri 2 + Rust Backend + React/TypeScript Frontend
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This plan implements a complete Lens Desktop v5.x-equivalent Kubernetes Management UI using the existing project architecture (Tauri + Rust + React). All features will be MIT-licensed, building on the foundation already established in the project.
|
||||||
|
|
||||||
|
**Current Status (v1.1.0):**
|
||||||
|
- ✅ 43 backend commands implemented in `src-tauri/src/commands/kube.rs`
|
||||||
|
- ✅ 115 command wrappers in `src/lib/tauriCommands.ts`
|
||||||
|
- ✅ Basic cluster management (add/remove/list)
|
||||||
|
- ✅ Port forwarding (start/stop/delete/shutdown)
|
||||||
|
- ✅ Resource discovery (pods, services, deployments, statefulsets, daemonsets, namespaces)
|
||||||
|
- ✅ Resource management (scale, restart, delete, exec)
|
||||||
|
- ✅ 22 additional resource types via backend commands
|
||||||
|
- ✅ Frontend components for 10 resource types (ClusterList, PodList, ServiceList, DeploymentList, StatefulSetList, DaemonSetList, PortForwardList, AddClusterModal, PortForwardForm, ResourceBrowser)
|
||||||
|
|
||||||
|
**What's Missing:**
|
||||||
|
- Frontend UI components for remaining 10+ resource types (Nodes, Events, ConfigMaps, Secrets, ReplicaSets, Jobs, CronJobs, Ingresses, PVCs, PVs, ServiceAccounts, Roles, ClusterRoles, RoleBindings, ClusterRoleBindings, HPAs)
|
||||||
|
- Advanced features (terminal, YAML editor, metrics, search, context switcher)
|
||||||
|
- Real-time updates via Kubernetes API watchers
|
||||||
|
- Multi-cluster context switching UI
|
||||||
|
- Application grouping
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Complete Resource Discovery UI (Priority: HIGH)
|
||||||
|
|
||||||
|
### 1.1 Nodes View
|
||||||
|
**File:** `src/components/Kubernetes/NodeList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of all cluster nodes
|
||||||
|
- Node status (Ready/NotReady)
|
||||||
|
- Roles (control-plane, worker)
|
||||||
|
- Kubernetes version
|
||||||
|
- Internal/external IPs
|
||||||
|
- OS image, kernel version, kubelet version
|
||||||
|
- Age
|
||||||
|
- Actions: Cordon, Uncordon, Drain, Shell, Edit, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_nodes()` - List all nodes
|
||||||
|
- `cordon_node()` - Mark node as unschedulable
|
||||||
|
- `uncordon_node()` - Mark node as schedulable
|
||||||
|
- `drain_node()` - Evict pods from node
|
||||||
|
|
||||||
|
### 1.2 Events View
|
||||||
|
**File:** `src/components/Kubernetes/EventList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of cluster events
|
||||||
|
- Event type (Normal/Warning)
|
||||||
|
- Reason (PodScheduled, Pulling, etc.)
|
||||||
|
- Object (pod name, deployment name)
|
||||||
|
- Count
|
||||||
|
- First seen, last seen
|
||||||
|
- Message
|
||||||
|
- Filter by namespace
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_events()` - List all events
|
||||||
|
|
||||||
|
### 1.3 ConfigMaps View
|
||||||
|
**File:** `src/components/Kubernetes/ConfigMapList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of configmaps
|
||||||
|
- Data keys count
|
||||||
|
- Age
|
||||||
|
- View/edit configmap data
|
||||||
|
- Delete configmap
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_configmaps()` - List all configmaps
|
||||||
|
- `create_resource()` - Create resource from YAML
|
||||||
|
- `edit_resource()` - Edit resource via YAML
|
||||||
|
- `delete_resource()` - Delete resource
|
||||||
|
|
||||||
|
### 1.4 Secrets View
|
||||||
|
**File:** `src/components/Kubernetes/SecretList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of secrets
|
||||||
|
- Secret type (Opaque, TLS, etc.)
|
||||||
|
- Data keys count
|
||||||
|
- Age
|
||||||
|
- Masked values (show ***)
|
||||||
|
- View/edit secret (YAML or form)
|
||||||
|
- Delete secret
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_secrets()` - List all secrets
|
||||||
|
|
||||||
|
### 1.5 ReplicaSets View
|
||||||
|
**File:** `src/components/Kubernetes/ReplicaSetList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of replica sets
|
||||||
|
- Desired/Ready replicas
|
||||||
|
- Age
|
||||||
|
- Labels
|
||||||
|
- Actions: View details, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_replicasets()` - List all replica sets
|
||||||
|
|
||||||
|
### 1.6 Jobs View
|
||||||
|
**File:** `src/components/Kubernetes/JobList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of jobs
|
||||||
|
- Completions (e.g., 1/1)
|
||||||
|
- Duration
|
||||||
|
- Age
|
||||||
|
- Status (Active/Succeeded/Failed)
|
||||||
|
- Actions: View logs, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_jobs()` - List all jobs
|
||||||
|
|
||||||
|
### 1.7 CronJobs View
|
||||||
|
**File:** `src/components/Kubernetes/CronJobList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of cronjobs
|
||||||
|
- Schedule (e.g., 0 * * * *)
|
||||||
|
- Active jobs count
|
||||||
|
- Last schedule
|
||||||
|
- Age
|
||||||
|
- Actions: View details, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_cronjobs()` - List all cronjobs
|
||||||
|
|
||||||
|
### 1.8 Ingresses View
|
||||||
|
**File:** `src/components/Kubernetes/IngressList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of ingresses
|
||||||
|
- Class (nginx, traefik, etc.)
|
||||||
|
- Host (domain)
|
||||||
|
- Addresses (load balancer IPs)
|
||||||
|
- Age
|
||||||
|
- Actions: View details, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_ingresses()` - List all ingresses
|
||||||
|
|
||||||
|
### 1.9 PersistentVolumeClaims View
|
||||||
|
**File:** `src/components/Kubernetes/PVCList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of PVCs
|
||||||
|
- Status (Pending/Bound/Lost)
|
||||||
|
- Volume (bound PV name)
|
||||||
|
- Capacity
|
||||||
|
- Access modes (RWO, ROX, etc.)
|
||||||
|
- Age
|
||||||
|
- Actions: Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_persistentvolumeclaims()` - List all PVCs
|
||||||
|
|
||||||
|
### 1.10 PersistentVolumes View
|
||||||
|
**File:** `src/components/Kubernetes/PVList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of PVs
|
||||||
|
- Status (Available/Bound/Released/Failed)
|
||||||
|
- Capacity
|
||||||
|
- Access modes
|
||||||
|
- Reclaim policy (Retain/Recycle/Delete)
|
||||||
|
- Storage class
|
||||||
|
- Age
|
||||||
|
- Actions: Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_persistentvolumes()` - List all PVs
|
||||||
|
|
||||||
|
### 1.11 ServiceAccounts View
|
||||||
|
**File:** `src/components/Kubernetes/ServiceAccountList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of service accounts
|
||||||
|
- Secrets count
|
||||||
|
- Age
|
||||||
|
- Actions: View details, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_serviceaccounts()` - List all service accounts
|
||||||
|
|
||||||
|
### 1.12 Roles View
|
||||||
|
**File:** `src/components/Kubernetes/RoleList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of roles
|
||||||
|
- Namespace
|
||||||
|
- Age
|
||||||
|
- Actions: View rules, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_roles()` - List all roles
|
||||||
|
|
||||||
|
### 1.13 ClusterRoles View
|
||||||
|
**File:** `src/components/Kubernetes/ClusterRoleList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of cluster roles
|
||||||
|
- Age
|
||||||
|
- Actions: View rules, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_clusterroles()` - List all cluster roles
|
||||||
|
|
||||||
|
### 1.14 RoleBindings View
|
||||||
|
**File:** `src/components/Kubernetes/RoleBindingList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of role bindings
|
||||||
|
- Namespace
|
||||||
|
- Role (reference)
|
||||||
|
- Age
|
||||||
|
- Actions: View details, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_rolebindings()` - List all role bindings
|
||||||
|
|
||||||
|
### 1.15 ClusterRoleBindings View
|
||||||
|
**File:** `src/components/Kubernetes/ClusterRoleBindingList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of cluster role bindings
|
||||||
|
- Cluster role (reference)
|
||||||
|
- Age
|
||||||
|
- Actions: View details, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_clusterrolebindings()` - List all cluster role bindings
|
||||||
|
|
||||||
|
### 1.16 HorizontalPodAutoscalers View
|
||||||
|
**File:** `src/components/Kubernetes/HPAList.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Table view of HPAs
|
||||||
|
- Min/Max replicas
|
||||||
|
- Current replicas
|
||||||
|
- Desired replicas
|
||||||
|
- Age
|
||||||
|
- Actions: View details, Delete
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_horizontalpodautoscalers()` - List all HPAs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Advanced Features (Priority: HIGH)
|
||||||
|
|
||||||
|
### 2.1 Interactive Terminal
|
||||||
|
**File:** `src/components/Kubernetes/Terminal.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Full-featured terminal using xterm.js
|
||||||
|
- Multiple tabs support
|
||||||
|
- Shell selection (sh, bash, zsh)
|
||||||
|
- Multi-container pod support
|
||||||
|
- Resize support
|
||||||
|
- Copy/paste
|
||||||
|
- Search in output
|
||||||
|
- Clear screen
|
||||||
|
- Disconnect/reconnect
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `exec_pod()` - Execute command in pod
|
||||||
|
|
||||||
|
**Implementation Notes:**
|
||||||
|
- Use `xterm.js` for terminal rendering
|
||||||
|
- Use `xterm-addon-web-links` for link detection
|
||||||
|
- Use `xterm-addon-fit` for auto-resize
|
||||||
|
- WebSocket-based terminal session (or kubectl exec)
|
||||||
|
|
||||||
|
### 2.2 YAML Editor
|
||||||
|
**File:** `src/components/Kubernetes/YamlEditor.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Code editor using Monaco (VS Code's editor)
|
||||||
|
- Syntax highlighting for YAML
|
||||||
|
- Validation (basic schema validation)
|
||||||
|
- Diff view (before/after)
|
||||||
|
- Apply button
|
||||||
|
- Cancel button
|
||||||
|
- Error messages
|
||||||
|
|
||||||
|
**Dependencies:**
|
||||||
|
- `@monaco-editor/react` (MIT licensed)
|
||||||
|
|
||||||
|
### 2.3 Metrics Visualization
|
||||||
|
**File:** `src/components/Kubernetes/MetricsChart.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- CPU usage chart (line/bar)
|
||||||
|
- Memory usage chart
|
||||||
|
- Time range selector (5m, 15m, 1h, 6h, 1d, 7d)
|
||||||
|
- Zoom functionality
|
||||||
|
- Legend
|
||||||
|
- Tooltip with values
|
||||||
|
- Per-container metrics for pods
|
||||||
|
|
||||||
|
**Backend Commands:**
|
||||||
|
- Need to add: `get_metrics()` for node/pod metrics
|
||||||
|
|
||||||
|
**Dependencies:**
|
||||||
|
- `react-chartjs-2` or `recharts` (MIT licensed)
|
||||||
|
|
||||||
|
### 2.4 Search and Filter
|
||||||
|
**File:** `src/components/Kubernetes/SearchBar.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Global search bar
|
||||||
|
- Search by name, labels, annotations
|
||||||
|
- Filter by namespace
|
||||||
|
- Filter by status
|
||||||
|
- Filter by resource type
|
||||||
|
- Recent searches
|
||||||
|
- Search suggestions
|
||||||
|
|
||||||
|
**Implementation Notes:**
|
||||||
|
- Debounced search
|
||||||
|
- Client-side filtering (or server-side for large datasets)
|
||||||
|
- Keyboard shortcuts (Ctrl+K)
|
||||||
|
|
||||||
|
### 2.5 Application Grouping
|
||||||
|
**File:** `src/components/Kubernetes/ApplicationView.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Group workloads by application label
|
||||||
|
- Visual hierarchy (app → deployment → pods)
|
||||||
|
- Resource relationships
|
||||||
|
- Dependency visualization
|
||||||
|
- Application status summary
|
||||||
|
|
||||||
|
**Implementation Notes:**
|
||||||
|
- Tree view component
|
||||||
|
- Use labels to group resources
|
||||||
|
- Show owner references
|
||||||
|
|
||||||
|
### 2.6 Context Switcher
|
||||||
|
**File:** `src/components/Kubernetes/ContextSwitcher.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Current cluster display
|
||||||
|
- Cluster selector dropdown
|
||||||
|
- Context selector (when multiple contexts in kubeconfig)
|
||||||
|
- Quick switch between clusters
|
||||||
|
- Visual indicator of active cluster
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `list_clusters()` - List all clusters
|
||||||
|
- `add_cluster()` - Add cluster
|
||||||
|
- `remove_cluster()` - Remove cluster
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Enhanced Workloads (Priority: HIGH)
|
||||||
|
|
||||||
|
### 3.1 Enhanced Pod List
|
||||||
|
**File:** `src/components/Kubernetes/PodList.tsx` (Update)
|
||||||
|
|
||||||
|
**Add Features:**
|
||||||
|
- Multi-container pod support (select container)
|
||||||
|
- Container status indicators
|
||||||
|
- Resource requests/limits display
|
||||||
|
- Node assignment
|
||||||
|
- IP address
|
||||||
|
- Restart count
|
||||||
|
- Events tab
|
||||||
|
- Logs streaming (auto-refresh)
|
||||||
|
|
||||||
|
### 3.2 Enhanced Deployment List
|
||||||
|
**File:** `src/components/Kubernetes/DeploymentList.tsx` (Update)
|
||||||
|
|
||||||
|
**Add Features:**
|
||||||
|
- Rollout status
|
||||||
|
- Revision history
|
||||||
|
- Rollback button
|
||||||
|
- Update strategy
|
||||||
|
- Progress conditions
|
||||||
|
- Events tab
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `rollback_deployment()` - Rollback deployment
|
||||||
|
|
||||||
|
### 3.3 Enhanced Service List
|
||||||
|
**File:** `src/components/Kubernetes/ServiceList.tsx` (Update)
|
||||||
|
|
||||||
|
**Add Features:**
|
||||||
|
- Endpoints display
|
||||||
|
- Selector display
|
||||||
|
- Session affinity
|
||||||
|
- Type-specific fields (LoadBalancer IPs, NodePorts)
|
||||||
|
- External name display
|
||||||
|
- Events tab
|
||||||
|
|
||||||
|
### 3.4 Enhanced ConfigMap/Secret View
|
||||||
|
**File:** `src/components/Kubernetes/ConfigMapDetail.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Data keys as expandable list
|
||||||
|
- Key-value pairs display
|
||||||
|
- Edit mode (form or YAML)
|
||||||
|
- Create new key
|
||||||
|
- Delete key
|
||||||
|
- Export to file
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Cluster Management (Priority: MEDIUM)
|
||||||
|
|
||||||
|
### 4.1 Cluster Overview
|
||||||
|
**File:** `src/components/Kubernetes/ClusterOverview.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Cluster name and version
|
||||||
|
- API server URL
|
||||||
|
- Provider information
|
||||||
|
- Node count (total, ready)
|
||||||
|
- Resource utilization (CPU, memory)
|
||||||
|
- Workload counts
|
||||||
|
- Quick actions (add cluster, refresh)
|
||||||
|
|
||||||
|
**Backend Commands:**
|
||||||
|
- `list_nodes()` - ✅ Implemented
|
||||||
|
- Need to add: `get_cluster_info()`
|
||||||
|
|
||||||
|
### 4.2 Cluster Details
|
||||||
|
**File:** `src/components/Kubernetes/ClusterDetails.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Cluster configuration
|
||||||
|
- Certificate details
|
||||||
|
- Storage classes
|
||||||
|
- Network policies
|
||||||
|
- RBAC summary
|
||||||
|
- Add-ons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: User Experience (Priority: MEDIUM)
|
||||||
|
|
||||||
|
### 5.1 Hotbar (Quick Actions)
|
||||||
|
**File:** `src/components/Kubernetes/Hotbar.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Quick access toolbar
|
||||||
|
- Common actions (refresh, create, search)
|
||||||
|
- Recent actions
|
||||||
|
- Custom shortcuts
|
||||||
|
|
||||||
|
### 5.2 Command Palette
|
||||||
|
**File:** `src/components/Kubernetes/CommandPalette.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Quick command access (Ctrl+Shift+P)
|
||||||
|
- Command search
|
||||||
|
- Keyboard shortcuts
|
||||||
|
- Recent commands
|
||||||
|
|
||||||
|
### 5.3 Toast Notifications
|
||||||
|
**File:** `src/components/Kubernetes/Toast.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Success/error notifications
|
||||||
|
- Auto-dismiss
|
||||||
|
- Action buttons in notifications
|
||||||
|
- History of notifications
|
||||||
|
|
||||||
|
### 5.4 Loading States
|
||||||
|
**File:** `src/components/Kubernetes/LoadingSpinner.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Loading indicators for all async operations
|
||||||
|
- Skeleton screens for data tables
|
||||||
|
- Progress indicators
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Advanced Management (Priority: LOW)
|
||||||
|
|
||||||
|
### 6.1 Resource Creation Dialogs
|
||||||
|
**File:** `src/components/Kubernetes/CreateResourceModal.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Create from template
|
||||||
|
- Create from YAML
|
||||||
|
- Create from form
|
||||||
|
- Namespace selection
|
||||||
|
- Validation
|
||||||
|
- Apply button
|
||||||
|
|
||||||
|
### 6.2 Resource Edit Dialog
|
||||||
|
**File:** `src/components/Kubernetes/EditResourceModal.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Edit existing resource
|
||||||
|
- YAML editor
|
||||||
|
- Form editor
|
||||||
|
- Preview changes
|
||||||
|
- Apply button
|
||||||
|
|
||||||
|
### 6.3 Port Forward UI
|
||||||
|
**File:** `src/components/Kubernetes/PortForwardForm.tsx` (Update)
|
||||||
|
|
||||||
|
**Add Features:**
|
||||||
|
- Pod selector
|
||||||
|
- Container selector
|
||||||
|
- Local port auto-detection
|
||||||
|
- Target port selection
|
||||||
|
- Multiple port forwards
|
||||||
|
- Active forwards list
|
||||||
|
|
||||||
|
**Backend Commands (✅ Implemented):**
|
||||||
|
- `start_port_forward()` - ✅ Implemented
|
||||||
|
- `stop_port_forward()` - ✅ Implemented
|
||||||
|
- `list_port_forwards()` - ✅ Implemented
|
||||||
|
|
||||||
|
### 6.4 Helm Integration
|
||||||
|
**File:** `src/components/Kubernetes/HelmView.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Charts view (from repositories)
|
||||||
|
- Releases view
|
||||||
|
- Install chart
|
||||||
|
- Upgrade release
|
||||||
|
- Rollback release
|
||||||
|
- Uninstall release
|
||||||
|
|
||||||
|
**Backend Commands:**
|
||||||
|
- Need to add: `helm_*` commands
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Real-time Updates (Priority: HIGH)
|
||||||
|
|
||||||
|
### 7.1 WebSocket Watchers
|
||||||
|
**File:** `src-tauri/src/kube/watcher.rs`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Kubernetes API watchers for all resource types
|
||||||
|
- Reconnect logic
|
||||||
|
- Resource caching with diff updates
|
||||||
|
- Real-time UI updates
|
||||||
|
- Performance optimization
|
||||||
|
|
||||||
|
**Implementation Notes:**
|
||||||
|
- Use `k8s-openapi` crate with `watch` feature
|
||||||
|
- Implement per-resource-type watchers
|
||||||
|
- Cache resources locally
|
||||||
|
- Push updates to frontend via Tauri events
|
||||||
|
|
||||||
|
### 7.2 Event Bus
|
||||||
|
**File:** `src/lib/eventBus.ts`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Centralized event system
|
||||||
|
- Resource change events
|
||||||
|
- Connection status events
|
||||||
|
- Error events
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: RBAC Management (Priority: MEDIUM)
|
||||||
|
|
||||||
|
### 8.1 RBAC Viewer
|
||||||
|
**File:** `src/components/Kubernetes/RbacViewer.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Role bindings visualization
|
||||||
|
- Reverse lookup (who has access to what)
|
||||||
|
- Permission checker
|
||||||
|
- Simulate policy
|
||||||
|
|
||||||
|
### 8.2 RBAC Editor
|
||||||
|
**File:** `src/components/Kubernetes/RbacEditor.tsx`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Create/edit roles
|
||||||
|
- Add/remove rules
|
||||||
|
- Bind roles to subjects
|
||||||
|
- Preview permissions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 9: Extension System (Priority: LOW)
|
||||||
|
|
||||||
|
### 9.1 Extension API
|
||||||
|
**File:** `src/lib/extensions.ts`
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Plugin architecture
|
||||||
|
- Extension loading
|
||||||
|
- Extension management UI
|
||||||
|
- Sandbox environment
|
||||||
|
|
||||||
|
**Implementation Notes:**
|
||||||
|
- Use WebAssembly for extensions
|
||||||
|
- Or use Node.js child processes
|
||||||
|
- Define extension API surface
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Order
|
||||||
|
|
||||||
|
### Sprint 1 (Week 1): Resource Discovery UI
|
||||||
|
- Nodes, Events, ConfigMaps, Secrets
|
||||||
|
- ReplicaSets, Jobs, CronJobs
|
||||||
|
- Ingresses, PVCs, PVs
|
||||||
|
- ServiceAccounts, Roles, ClusterRoles
|
||||||
|
- RoleBindings, ClusterRoleBindings, HPAs
|
||||||
|
|
||||||
|
### Sprint 2 (Week 2): Advanced Features
|
||||||
|
- Interactive terminal
|
||||||
|
- YAML editor
|
||||||
|
- Metrics visualization
|
||||||
|
- Search and filter
|
||||||
|
- Application grouping
|
||||||
|
- Context switcher
|
||||||
|
|
||||||
|
### Sprint 3 (Week 3): Enhanced Workloads
|
||||||
|
- Enhanced Pod/Deployment/Service lists
|
||||||
|
- ConfigMap/Secret detail views
|
||||||
|
- Cluster overview
|
||||||
|
- Cluster details
|
||||||
|
|
||||||
|
### Sprint 4 (Week 4): UX & Polish
|
||||||
|
- Hotbar, Command palette
|
||||||
|
- Toast notifications
|
||||||
|
- Loading states
|
||||||
|
- Resource creation/edit dialogs
|
||||||
|
- Port forward UI
|
||||||
|
|
||||||
|
### Sprint 5 (Week 5): Real-time & RBAC
|
||||||
|
- WebSocket watchers
|
||||||
|
- Event bus
|
||||||
|
- RBAC viewer/editor
|
||||||
|
- Extension system (optional)
|
||||||
|
|
||||||
|
### Sprint 6 (Week 6): Testing & Release
|
||||||
|
- Test coverage
|
||||||
|
- Documentation
|
||||||
|
- Bug fixes
|
||||||
|
- Release preparation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies to Add
|
||||||
|
|
||||||
|
### Frontend (npm):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"xterm": "^5.3.0",
|
||||||
|
"xterm-addon-web-links": "^0.9.0",
|
||||||
|
"xterm-addon-fit": "^0.8.0",
|
||||||
|
"@monaco-editor/react": "^4.6.0",
|
||||||
|
"react-chartjs-2": "^5.2.0",
|
||||||
|
"chart.js": "^4.4.0",
|
||||||
|
"zustand": "^4.4.0" (already present)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend (Cargo.toml):
|
||||||
|
```toml
|
||||||
|
# For Kubernetes API watchers
|
||||||
|
k8s-openapi = { version = "0.21", features = ["watch"] }
|
||||||
|
tokio-stream = "1.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture Updates
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
- Add `clusters` store (persisted)
|
||||||
|
- Add `portForwards` store (persisted)
|
||||||
|
- Add `selectedContext` store (ephemeral)
|
||||||
|
- Add `resources` store (cached, with watchers)
|
||||||
|
|
||||||
|
### Backend Enhancements
|
||||||
|
- Add `ResourceCache` struct for efficient local caching
|
||||||
|
- Add `ResourceWatcher` for Kubernetes API watchers
|
||||||
|
- Add `EventBus` for real-time updates
|
||||||
|
- Add `MetricsCollector` for resource metrics
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
✅ **100% Feature Parity Checklist:**
|
||||||
|
|
||||||
|
### Core Features (Must Have)
|
||||||
|
- [ ] All 16 resource discovery UIs implemented
|
||||||
|
- [ ] All 6 management UIs implemented
|
||||||
|
- [ ] Interactive terminal with tab support
|
||||||
|
- [ ] YAML editor with validation
|
||||||
|
- [ ] Metrics visualization
|
||||||
|
- [ ] Search and filter functionality
|
||||||
|
- [ ] Application grouping
|
||||||
|
- [ ] Context switcher
|
||||||
|
- [ ] Real-time updates via watchers
|
||||||
|
- [ ] RBAC viewer/editor
|
||||||
|
|
||||||
|
### Quality Features (Should Have)
|
||||||
|
- [ ] Hotbar and command palette
|
||||||
|
- [ ] Toast notifications
|
||||||
|
- [ ] Loading states
|
||||||
|
- [ ] Resource creation/edit dialogs
|
||||||
|
- [ ] Port forward UI
|
||||||
|
- [ ] Helm integration
|
||||||
|
- [ ] Cluster overview
|
||||||
|
- [ ] RBAC management
|
||||||
|
|
||||||
|
### Enterprise Features (Nice to Have)
|
||||||
|
- [ ] Extension system
|
||||||
|
- [ ] Multi-cluster management UI
|
||||||
|
- [ ] Team sharing
|
||||||
|
- [ ] Audit trail enhancements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risk Assessment
|
||||||
|
|
||||||
|
| Risk | Impact | Mitigation |
|
||||||
|
|------|--------|------------|
|
||||||
|
| Backend command implementation | HIGH | Already done (43 commands) |
|
||||||
|
| Frontend component complexity | MEDIUM | Use existing patterns |
|
||||||
|
| Real-time performance | MEDIUM | Implement caching and diff updates |
|
||||||
|
| Terminal integration | LOW | Use xterm.js library |
|
||||||
|
| Metrics collection | MEDIUM | Add `get_metrics()` command |
|
||||||
|
| Helm integration | LOW | Optional feature |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All implementations must remain MIT licensed
|
||||||
|
- Follow existing code patterns in the project
|
||||||
|
- Use existing UI components from `src/components/ui/index.tsx`
|
||||||
|
- Test each feature before moving to next
|
||||||
|
- Update `RELEASE_NOTES.md` for each phase
|
||||||
|
- Update `README.md` with new features
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Document Version:** 1.0
|
||||||
|
**Last Updated:** 2026-06-07
|
||||||
|
**Next Review:** After Sprint 1 completion
|
||||||
391
eslint.config.js
391
eslint.config.js
@ -6,267 +6,136 @@ import parserTs from "@typescript-eslint/parser";
|
|||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
files: ["src/**/*.{ts,tsx}"],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
globals: {
|
|
||||||
...globals.browser,
|
|
||||||
...globals.node,
|
|
||||||
},
|
|
||||||
parser: parserTs,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
react: pluginReact,
|
|
||||||
"react-hooks": pluginReactHooks,
|
|
||||||
"@typescript-eslint": pluginTs,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...pluginReact.configs.recommended.rules,
|
|
||||||
...pluginReactHooks.configs.recommended.rules,
|
|
||||||
...pluginTs.configs.recommended.rules,
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
||||||
"react/react-in-jsx-scope": "off",
|
|
||||||
"react/prop-types": "off",
|
|
||||||
"react/no-unescaped-entities": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["tests/unit/**/*.test.{ts,tsx}", "tests/unit/setup.ts"],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
globals: {
|
|
||||||
...globals.browser,
|
|
||||||
...globals.node,
|
|
||||||
...globals.vitest,
|
|
||||||
},
|
|
||||||
parser: parserTs,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
react: pluginReact,
|
|
||||||
"react-hooks": pluginReactHooks,
|
|
||||||
"@typescript-eslint": pluginTs,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...pluginReact.configs.recommended.rules,
|
|
||||||
...pluginReactHooks.configs.recommended.rules,
|
|
||||||
...pluginTs.configs.recommended.rules,
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
||||||
"react/react-in-jsx-scope": "off",
|
|
||||||
"react/prop-types": "off",
|
|
||||||
"react/no-unescaped-entities": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["tests/e2e/**/*.ts", "tests/e2e/**/*.tsx"],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
},
|
|
||||||
parser: parserTs,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
"@typescript-eslint": pluginTs,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...pluginTs.configs.recommended.rules,
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["cli/**/*.{ts,tsx}"],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
},
|
|
||||||
parser: parserTs,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
"@typescript-eslint": pluginTs,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...pluginTs.configs.recommended.rules,
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
||||||
"react/no-unescaped-entities": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["src/**/*.{ts,tsx}"],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
globals: {
|
|
||||||
...globals.browser,
|
|
||||||
...globals.node,
|
|
||||||
},
|
|
||||||
parser: parserTs,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
react: pluginReact,
|
|
||||||
"react-hooks": pluginReactHooks,
|
|
||||||
"@typescript-eslint": pluginTs,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...pluginReact.configs.recommended.rules,
|
|
||||||
...pluginReactHooks.configs.recommended.rules,
|
|
||||||
...pluginTs.configs.recommended.rules,
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
||||||
"react/react-in-jsx-scope": "off",
|
|
||||||
"react/prop-types": "off",
|
|
||||||
"react/no-unescaped-entities": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["tests/unit/**/*.test.{ts,tsx}", "tests/unit/setup.ts"],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
globals: {
|
|
||||||
...globals.browser,
|
|
||||||
...globals.node,
|
|
||||||
...globals.vitest,
|
|
||||||
},
|
|
||||||
parser: parserTs,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
react: pluginReact,
|
|
||||||
"react-hooks": pluginReactHooks,
|
|
||||||
"@typescript-eslint": pluginTs,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...pluginReact.configs.recommended.rules,
|
|
||||||
...pluginReactHooks.configs.recommended.rules,
|
|
||||||
...pluginTs.configs.recommended.rules,
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
||||||
"react/react-in-jsx-scope": "off",
|
|
||||||
"react/prop-types": "off",
|
|
||||||
"react/no-unescaped-entities": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["tests/e2e/**/*.ts", "tests/e2e/**/*.tsx"],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
},
|
|
||||||
parser: parserTs,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
"@typescript-eslint": pluginTs,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...pluginTs.configs.recommended.rules,
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["cli/**/*.{ts,tsx}"],
|
|
||||||
languageOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
},
|
|
||||||
parser: parserTs,
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
"@typescript-eslint": pluginTs,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
...pluginTs.configs.recommended.rules,
|
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
||||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
|
||||||
"react/no-unescaped-entities": "off",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
files: ["**/*.{js,jsx,mjs,cjs,ts,tsx}"],
|
|
||||||
ignores: ["dist/", "node_modules/", "src-tauri/target/**", "target/**", "coverage/", "tailwind.config.ts"],
|
ignores: ["dist/", "node_modules/", "src-tauri/target/**", "target/**", "coverage/", "tailwind.config.ts"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
files: ["src/**/*.{ts,tsx}"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parser: parserTs,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
react: pluginReact,
|
||||||
|
"react-hooks": pluginReactHooks,
|
||||||
|
"@typescript-eslint": pluginTs,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...pluginReact.configs.recommended.rules,
|
||||||
|
...pluginReactHooks.configs.recommended.rules,
|
||||||
|
...pluginTs.configs.recommended.rules,
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||||
|
"no-console": ["warn", { allow: ["warn", "error"] }],
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
"react/no-unescaped-entities": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["tests/unit/**/*.test.{ts,tsx}", "tests/unit/setup.ts"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node,
|
||||||
|
...globals.vitest,
|
||||||
|
},
|
||||||
|
parser: parserTs,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
react: pluginReact,
|
||||||
|
"react-hooks": pluginReactHooks,
|
||||||
|
"@typescript-eslint": pluginTs,
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...pluginReact.configs.recommended.rules,
|
||||||
|
...pluginReactHooks.configs.recommended.rules,
|
||||||
|
...pluginTs.configs.recommended.rules,
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||||
|
"no-console": ["warn", { allow: ["warn", "error"] }],
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
"react/no-unescaped-entities": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["tests/e2e/**/*.ts", "tests/e2e/**/*.tsx"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parser: parserTs,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
"@typescript-eslint": pluginTs,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...pluginTs.configs.recommended.rules,
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||||
|
"no-console": ["warn", { allow: ["warn", "error"] }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["cli/**/*.{ts,tsx}"],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
parser: parserTs,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
"@typescript-eslint": pluginTs,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...pluginTs.configs.recommended.rules,
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
||||||
|
"no-console": ["warn", { allow: ["log", "warn", "error"] }],
|
||||||
|
"react/no-unescaped-entities": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
204
lens-desktop-v5x-features.md
Normal file
204
lens-desktop-v5x-features.md
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# Lens Desktop v5.x Feature Research Summary
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This research compiles a comprehensive feature list for Lens Desktop v5.x (the last open source version before it went proprietary). Lens Desktop was acquired by Mirantis and transitioned from open source to proprietary/enterprise model. The features documented represent what was available in v5.x before the transition, with "Premium" features likely being core/open features in v5.x that later became enterprise-only.
|
||||||
|
|
||||||
|
## Research Context
|
||||||
|
|
||||||
|
- **Lens Desktop v5.x**: Last open source version before Mirantis acquisition
|
||||||
|
- **Current Status**: Transitioned to proprietary Lens K8S IDE with premium features
|
||||||
|
- **Key Differentiator**: First Kubernetes IDE with integrated AI assistant (Lens Prism)
|
||||||
|
|
||||||
|
## Feature Categories
|
||||||
|
|
||||||
|
### 1. UI Features and Components
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Navigator | UI | No | Sidebar navigation for cluster resources and management |
|
||||||
|
| Hotbar | UI | No | Quick access toolbar for common actions and commands |
|
||||||
|
| Terminal | UI | No | Built-in terminal for direct cluster interaction |
|
||||||
|
| Details panel | UI | No | Detailed view of selected Kubernetes resources |
|
||||||
|
| Applications view | UI | No | Visual representation of applications and components |
|
||||||
|
| Nodes view | UI | No | View and manage Kubernetes nodes with resource utilization |
|
||||||
|
| Lens K8S IDE layout | UI | No | Structured workspace layout for Kubernetes management |
|
||||||
|
| Preferences | UI | No | User preferences and settings |
|
||||||
|
|
||||||
|
### 2. Workload Management Features
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Pods view | Workloads | No | View and manage pods with status, logs, and actions |
|
||||||
|
| Deployments view | Workloads | No | Manage deployments with scaling, updates, and rollouts |
|
||||||
|
| Daemon Sets view | Workloads | No | View and manage daemon sets across nodes |
|
||||||
|
| Stateful Sets view | Workloads | No | Manage stateful applications and persistent storage |
|
||||||
|
| Replica Sets view | Workloads | No | View and manage replica sets |
|
||||||
|
| Replication Controllers view | Workloads | No | Manage replication controllers |
|
||||||
|
| Jobs view | Workloads | No | View and manage batch jobs |
|
||||||
|
| Cron Jobs view | Workloads | No | Manage scheduled cron jobs |
|
||||||
|
|
||||||
|
### 3. Config Management Features
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Config Maps view | Config | No | View and manage configuration maps |
|
||||||
|
| Secrets view | Config | No | Manage sensitive data and credentials |
|
||||||
|
| Resource Quotas view | Config | No | View resource quotas per namespace |
|
||||||
|
| Limit Ranges view | Config | No | Manage resource limits in namespaces |
|
||||||
|
| Horizontal Pod Autoscalers view | Config | No | View and manage HPAs |
|
||||||
|
| Vertical Pod Autoscalers view | Config | No | View and manage VPAs |
|
||||||
|
| Pod Disruption Budgets view | Config | No | Manage pod disruption budgets |
|
||||||
|
| Priority Classes view | Config | No | View and manage priority classes |
|
||||||
|
| Runtime Classes view | Config | No | Manage different container runtime configurations |
|
||||||
|
| Mutating Webhook Configs | Config | No | Mutating webhook configurations |
|
||||||
|
| Validating Webhook Configs | Config | No | Validating webhook configurations |
|
||||||
|
| Admission Policies | Config | No | Manage admission control policies |
|
||||||
|
|
||||||
|
### 4. Network Features
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Services view | Network | No | View and manage Kubernetes services |
|
||||||
|
| Endpoints view | Network | No | View service endpoints |
|
||||||
|
| Endpoint Slices view | Network | No | Manage endpoint slices for large services |
|
||||||
|
| Gateway API resources | Network | No | Manage service mesh and gateway configurations |
|
||||||
|
| Ingresses view | Network | No | View and manage ingress resources |
|
||||||
|
| Ingress Classes view | Network | No | Manage ingress controller classes |
|
||||||
|
| Network Policies view | Network | No | View and manage network policies |
|
||||||
|
| Port Forwarding view | Network | No | Manage port forwarding rules |
|
||||||
|
|
||||||
|
### 5. Storage Features
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Persistent Volume Claims view | Storage | No | View and manage PVCs |
|
||||||
|
| Persistent Volumes view | Storage | No | Manage persistent volumes |
|
||||||
|
| Storage Classes view | Storage | No | View and manage storage classes |
|
||||||
|
|
||||||
|
### 6. Cluster Management Features
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Add AWS EKS clusters (One-Click) | Cluster | Yes | One-click integration for AWS EKS clusters |
|
||||||
|
| Add Azure AKS clusters (One-Click) | Cluster | Yes | One-click integration for Azure AKS clusters |
|
||||||
|
| Add Google GKE clusters | Cluster | No | Add Google Kubernetes Engine clusters |
|
||||||
|
| Add Red Hat OpenShift clusters | Cluster | No | Add OpenShift clusters |
|
||||||
|
| View cluster details | Cluster | No | Comprehensive cluster information and status |
|
||||||
|
| Cluster settings | Cluster | No | Configure cluster-specific settings |
|
||||||
|
| Enable cluster metrics | Cluster | No | Enable and view cluster metrics |
|
||||||
|
| Public cloud services | Cluster | No | Integration with public cloud providers |
|
||||||
|
| Create cluster resources | Cluster | No | Create resources directly from the UI |
|
||||||
|
| Cluster Performance | Cluster | No | Monitor cluster performance metrics |
|
||||||
|
|
||||||
|
### 7. User Workflow Features
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Find a cluster | Workflow | No | Quick cluster discovery and selection |
|
||||||
|
| Find a deployment | Workflow | No | Quick deployment search |
|
||||||
|
| View logs | Workflow | No | Stream and view container logs |
|
||||||
|
| Open Pod Shell | Workflow | No | Interactive shell access to pods |
|
||||||
|
| Port forward traffic | Workflow | No | Port forwarding functionality |
|
||||||
|
| Modify a deployment | Workflow | No | Edit deployment configurations |
|
||||||
|
| Restart a deployment | Workflow | No | Restart deployments with zero downtime |
|
||||||
|
| Manage Helm charts | Workflow | No | Helm chart management and deployment |
|
||||||
|
| Use Command Palette | Workflow | No | Quick command access via command palette |
|
||||||
|
| Lens CLI | Workflow | No | Command-line interface for Lens operations |
|
||||||
|
|
||||||
|
### 8. Premium Features (Enterprise-only post-v5.x)
|
||||||
|
|
||||||
|
| Feature | Category | Description |
|
||||||
|
|---------|----------|-------------|
|
||||||
|
| Lens Prism | AI | Built-in AI assistant for Kubernetes exploration and troubleshooting |
|
||||||
|
| Lens Agents | AI | Platform for running AI agents on enterprise systems |
|
||||||
|
| Org-Wide AI Governance Rollout | Governance | Enterprise-wide AI governance deployment |
|
||||||
|
| EU AI Act Readiness | Compliance | Compliance features for EU AI Act requirements |
|
||||||
|
| Hardened Lens K8S IDE | Security | Enterprise-hardened version with feature control |
|
||||||
|
| Air-gapped mode | Deployment | Support for air-gapped environments |
|
||||||
|
| Offline activation mode | Licensing | Offline license activation |
|
||||||
|
| Lens Business ID | Identity | Enterprise account management with SSO/SCIM |
|
||||||
|
| Organizations, Teams & Projects | Governance | Enterprise organizational structure |
|
||||||
|
| Identity & Authentication | Security | Enterprise identity management |
|
||||||
|
| Audit Trail | Security | Comprehensive audit logging |
|
||||||
|
| Security Whitepaper | Security | Security documentation and compliance |
|
||||||
|
| Compliance | Security | Compliance management features |
|
||||||
|
| Privacy & PII Controls | Security | Personal data protection controls |
|
||||||
|
| Data Sovereignty | Security | Data sovereignty and location controls |
|
||||||
|
|
||||||
|
### 9. Access Control Features
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Service Accounts view | Access Control | No | Service account management |
|
||||||
|
| Cluster Roles view | Access Control | No | Cluster role management |
|
||||||
|
| Roles view | Access Control | No | Role management within namespaces |
|
||||||
|
| Cluster Role Bindings view | Access Control | No | Cluster role binding management |
|
||||||
|
| Role Bindings view | Access Control | No | Role binding management |
|
||||||
|
| Pod Security Policies view | Access Control | No | Pod security policy management |
|
||||||
|
|
||||||
|
### 10. Helm Features
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Charts view | Helm | No | Helm chart repository management |
|
||||||
|
| Releases view | Helm | No | Helm release management |
|
||||||
|
|
||||||
|
### 11. Lens Teamwork Features
|
||||||
|
|
||||||
|
| Feature | Category | Premium | Description |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| Create a team space | Teamwork | No | Create collaborative team spaces |
|
||||||
|
| Add a cluster to a team space | Teamwork | No | Share clusters across team spaces |
|
||||||
|
|
||||||
|
## Key Differentiators (What Made Lens Complete)
|
||||||
|
|
||||||
|
1. **Built-in AI Assistant (Lens Prism)**: One of the first IDEs with integrated AI for Kubernetes exploration and troubleshooting
|
||||||
|
|
||||||
|
2. **Enterprise AI Governance (Lens Agents)**: Unique platform for running and governing AI agents on enterprise systems
|
||||||
|
|
||||||
|
3. **One-Click Cloud Integration**: Easy integration with major cloud providers (AWS, Azure, GKE)
|
||||||
|
|
||||||
|
4. **Comprehensive Premium Security Features**: Enterprise-grade security, compliance, and governance capabilities
|
||||||
|
|
||||||
|
5. **Full Kubernetes Resource Management**: Complete coverage of all Kubernetes resource types from workloads to access control
|
||||||
|
|
||||||
|
6. **Integrated Terminal and Shell Access**: Direct cluster interaction without leaving the IDE
|
||||||
|
|
||||||
|
7. **Advanced Workload Visualization**: Visual representation of applications and their relationships
|
||||||
|
|
||||||
|
8. **AI Agent Execution with Sandbox Isolation**: Secure, isolated execution environment for AI agents
|
||||||
|
|
||||||
|
9. **Agent-Hour Usage Tracking**: Unique metering system for AI agent operations
|
||||||
|
|
||||||
|
10. **Enterprise Policy Controls**: Granular policy enforcement for enterprise environments
|
||||||
|
|
||||||
|
## Comparison with Alternatives
|
||||||
|
|
||||||
|
### vs k9s
|
||||||
|
- **Lens Advantage**: GUI with visual workload representation, integrated terminal, AI assistant, cloud integrations
|
||||||
|
- **k9s Advantage**: CLI-based (no GUI overhead), lighter weight, faster startup
|
||||||
|
|
||||||
|
### vs Headlamp
|
||||||
|
- **Lens Advantage**: More mature UI, AI assistant, enterprise features, commercial support
|
||||||
|
- **Headlamp Advantage**: Open source, plugin architecture, lightweight
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
Lens Desktop v5.x represented a comprehensive Kubernetes management GUI with features that rivaled or exceeded commercial tools of its time. The transition to proprietary model added enterprise features (AI governance, compliance, security) while some core features may have been repackaged as premium offerings.
|
||||||
|
|
||||||
|
For building a similar tool, the key areas to focus on are:
|
||||||
|
1. Complete Kubernetes resource coverage
|
||||||
|
2. Integrated development environment features (terminal, shell access)
|
||||||
|
3. Visual workload representation and navigation
|
||||||
|
4. Cloud provider integrations
|
||||||
|
5. Enterprise security and compliance features
|
||||||
|
6. AI assistant capabilities (optional but differentiating)
|
||||||
|
|
||||||
|
## Research Notes
|
||||||
|
|
||||||
|
- The "v5.x" designation isn't explicitly mentioned in current documentation, but the transition point from open source to proprietary is clear
|
||||||
|
- Current Lens documentation shows premium features that were likely core features in v5.x
|
||||||
|
- Lens uses Electron framework for desktop application
|
||||||
|
- AI features (Lens Prism) were added post-v5.x as part of the proprietary transition
|
||||||
|
- One-Click AWS and Azure integrations were premium features, suggesting they may have been community plugins or missing in v5.x
|
||||||
File diff suppressed because it is too large
Load Diff
@ -187,6 +187,43 @@ pub fn run() {
|
|||||||
commands::kube::shutdown_port_forwards,
|
commands::kube::shutdown_port_forwards,
|
||||||
commands::kube::test_cluster_connection,
|
commands::kube::test_cluster_connection,
|
||||||
commands::kube::discover_pods,
|
commands::kube::discover_pods,
|
||||||
|
// Kubernetes Resource Discovery
|
||||||
|
commands::kube::list_namespaces,
|
||||||
|
commands::kube::list_pods,
|
||||||
|
commands::kube::list_services,
|
||||||
|
commands::kube::list_deployments,
|
||||||
|
commands::kube::list_statefulsets,
|
||||||
|
commands::kube::list_daemonsets,
|
||||||
|
// Additional Kubernetes Resource Discovery
|
||||||
|
commands::kube::list_replicasets,
|
||||||
|
commands::kube::list_jobs,
|
||||||
|
commands::kube::list_cronjobs,
|
||||||
|
commands::kube::list_configmaps,
|
||||||
|
commands::kube::list_secrets,
|
||||||
|
commands::kube::list_nodes,
|
||||||
|
commands::kube::list_events,
|
||||||
|
commands::kube::list_ingresses,
|
||||||
|
commands::kube::list_persistentvolumeclaims,
|
||||||
|
commands::kube::list_persistentvolumes,
|
||||||
|
commands::kube::list_serviceaccounts,
|
||||||
|
commands::kube::list_roles,
|
||||||
|
commands::kube::list_clusterroles,
|
||||||
|
commands::kube::list_rolebindings,
|
||||||
|
commands::kube::list_clusterrolebindings,
|
||||||
|
commands::kube::list_horizontalpodautoscalers,
|
||||||
|
// Kubernetes Resource Management
|
||||||
|
commands::kube::get_pod_logs,
|
||||||
|
commands::kube::scale_deployment,
|
||||||
|
commands::kube::restart_deployment,
|
||||||
|
commands::kube::delete_resource,
|
||||||
|
commands::kube::exec_pod,
|
||||||
|
// Additional Kubernetes Resource Management
|
||||||
|
commands::kube::cordon_node,
|
||||||
|
commands::kube::uncordon_node,
|
||||||
|
commands::kube::drain_node,
|
||||||
|
commands::kube::rollback_deployment,
|
||||||
|
commands::kube::create_resource,
|
||||||
|
commands::kube::edit_resource,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("Error running Troubleshooting and RCA Assistant application");
|
.expect("Error running Troubleshooting and RCA Assistant application");
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
Moon,
|
Moon,
|
||||||
Terminal,
|
Terminal,
|
||||||
FileCode,
|
FileCode,
|
||||||
|
Server,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useSettingsStore } from "@/stores/settingsStore";
|
import { useSettingsStore } from "@/stores/settingsStore";
|
||||||
import { getAppVersionCmd, loadAiProvidersCmd, testProviderConnectionCmd, shutdownPortForwardsCmd } from "@/lib/tauriCommands";
|
import { getAppVersionCmd, loadAiProvidersCmd, testProviderConnectionCmd, shutdownPortForwardsCmd } from "@/lib/tauriCommands";
|
||||||
@ -34,11 +35,13 @@ import MCPServers from "@/pages/Settings/MCPServers";
|
|||||||
import Security from "@/pages/Settings/Security";
|
import Security from "@/pages/Settings/Security";
|
||||||
import ShellExecution from "@/pages/Settings/ShellExecution";
|
import ShellExecution from "@/pages/Settings/ShellExecution";
|
||||||
import KubeconfigManager from "@/pages/Settings/KubeconfigManager";
|
import KubeconfigManager from "@/pages/Settings/KubeconfigManager";
|
||||||
|
import { KubernetesPage } from "@/pages/Kubernetes/KubernetesPage";
|
||||||
import { ShellApprovalModal } from "@/components/ShellApprovalModal";
|
import { ShellApprovalModal } from "@/components/ShellApprovalModal";
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ to: "/", icon: Home, label: "Dashboard" },
|
{ to: "/", icon: Home, label: "Dashboard" },
|
||||||
{ to: "/new-issue", icon: Plus, label: "New Issue" },
|
{ to: "/new-issue", icon: Plus, label: "New Issue" },
|
||||||
|
{ to: "/kubernetes", icon: Server, label: "Kubernetes" },
|
||||||
{ to: "/history", icon: Clock, label: "History" },
|
{ to: "/history", icon: Clock, label: "History" },
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -85,10 +88,8 @@ export default function App() {
|
|||||||
// Auto-test the active provider
|
// Auto-test the active provider
|
||||||
const activeProvider = getActiveProvider();
|
const activeProvider = getActiveProvider();
|
||||||
if (activeProvider) {
|
if (activeProvider) {
|
||||||
console.log("Auto-testing active AI provider:", activeProvider.name);
|
|
||||||
try {
|
try {
|
||||||
await testProviderConnectionCmd(activeProvider);
|
await testProviderConnectionCmd(activeProvider);
|
||||||
console.log("✓ Active provider connection verified:", activeProvider.name);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("⚠ Active provider connection test failed:", activeProvider.name, err);
|
console.warn("⚠ Active provider connection test failed:", activeProvider.name, err);
|
||||||
}
|
}
|
||||||
@ -197,6 +198,7 @@ export default function App() {
|
|||||||
<Route path="/settings/ollama" element={<Ollama />} />
|
<Route path="/settings/ollama" element={<Ollama />} />
|
||||||
<Route path="/settings/shell" element={<ShellExecution />} />
|
<Route path="/settings/shell" element={<ShellExecution />} />
|
||||||
<Route path="/settings/kubeconfig" element={<KubeconfigManager />} />
|
<Route path="/settings/kubeconfig" element={<KubeconfigManager />} />
|
||||||
|
<Route path="/kubernetes" element={<KubernetesPage />} />
|
||||||
<Route path="/settings/integrations" element={<Integrations />} />
|
<Route path="/settings/integrations" element={<Integrations />} />
|
||||||
<Route path="/settings/mcp" element={<MCPServers />} />
|
<Route path="/settings/mcp" element={<MCPServers />} />
|
||||||
<Route path="/settings/security" element={<Security />} />
|
<Route path="/settings/security" element={<Security />} />
|
||||||
|
|||||||
50
src/components/Kubernetes/DaemonSetList.tsx
Normal file
50
src/components/Kubernetes/DaemonSetList.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
|
||||||
|
import type { DaemonSetInfo } from "@/lib/tauriCommands";
|
||||||
|
|
||||||
|
interface DaemonSetListProps {
|
||||||
|
daemonsets: DaemonSetInfo[];
|
||||||
|
clusterId: string;
|
||||||
|
namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DaemonSetList({ daemonsets, clusterId: _clusterId, namespace: _namespace }: DaemonSetListProps) {
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Desired</TableHead>
|
||||||
|
<TableHead>Current</TableHead>
|
||||||
|
<TableHead>Ready</TableHead>
|
||||||
|
<TableHead>Up-to-date</TableHead>
|
||||||
|
<TableHead>Available</TableHead>
|
||||||
|
<TableHead>Age</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{daemonsets.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={7} className="text-center text-muted-foreground">
|
||||||
|
No daemonsets found
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
daemonsets.map((ds) => (
|
||||||
|
<TableRow key={ds.name}>
|
||||||
|
<TableCell className="font-medium">{ds.name}</TableCell>
|
||||||
|
<TableCell>{ds.desired}</TableCell>
|
||||||
|
<TableCell>{ds.current}</TableCell>
|
||||||
|
<TableCell>{ds.ready}</TableCell>
|
||||||
|
<TableCell>{ds.up_to_date}</TableCell>
|
||||||
|
<TableCell>{ds.available}</TableCell>
|
||||||
|
<TableCell className="text-muted-foreground">{ds.age}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
208
src/components/Kubernetes/DeploymentList.tsx
Normal file
208
src/components/Kubernetes/DeploymentList.tsx
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui";
|
||||||
|
import { Input } from "@/components/ui";
|
||||||
|
import { Label } from "@/components/ui";
|
||||||
|
import { Alert, AlertDescription } from "@/components/ui";
|
||||||
|
import { AlertCircle, RotateCcw, Scale } from "lucide-react";
|
||||||
|
import type { DeploymentInfo } from "@/lib/tauriCommands";
|
||||||
|
|
||||||
|
interface DeploymentListProps {
|
||||||
|
deployments: DeploymentInfo[];
|
||||||
|
clusterId: string;
|
||||||
|
namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeploymentList({ deployments, clusterId, namespace }: DeploymentListProps) {
|
||||||
|
const [scalingDeployment, setScalingDeployment] = useState<DeploymentInfo | null>(null);
|
||||||
|
const [replicas, setReplicas] = useState<string>("");
|
||||||
|
const [isScaling, setIsScaling] = useState(false);
|
||||||
|
const [scaleError, setScaleError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [restartingDeployment, setRestartingDeployment] = useState<DeploymentInfo | null>(null);
|
||||||
|
const [isRestarting, setIsRestarting] = useState(false);
|
||||||
|
const [restartError, setRestartError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleScaleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setReplicas(e.target.value);
|
||||||
|
setScaleError(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScaleSubmit = async () => {
|
||||||
|
if (!scalingDeployment) return;
|
||||||
|
|
||||||
|
const newReplicas = parseInt(replicas, 10);
|
||||||
|
if (isNaN(newReplicas) || newReplicas < 0) {
|
||||||
|
setScaleError("Invalid replica count");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsScaling(true);
|
||||||
|
setScaleError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await invoke<void>("scale_deployment", {
|
||||||
|
clusterId,
|
||||||
|
namespace,
|
||||||
|
deploymentName: scalingDeployment.name,
|
||||||
|
replicas: newReplicas,
|
||||||
|
});
|
||||||
|
|
||||||
|
setScalingDeployment(null);
|
||||||
|
setReplicas("");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to scale deployment:", err);
|
||||||
|
setScaleError(err instanceof Error ? err.message : "Failed to scale deployment");
|
||||||
|
} finally {
|
||||||
|
setIsScaling(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRestartSubmit = async () => {
|
||||||
|
if (!restartingDeployment) return;
|
||||||
|
|
||||||
|
setIsRestarting(true);
|
||||||
|
setRestartError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await invoke<void>("restart_deployment", {
|
||||||
|
clusterId,
|
||||||
|
namespace,
|
||||||
|
deploymentName: restartingDeployment.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
setRestartingDeployment(null);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to restart deployment:", err);
|
||||||
|
setRestartError(err instanceof Error ? err.message : "Failed to restart deployment");
|
||||||
|
} finally {
|
||||||
|
setIsRestarting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Ready</TableHead>
|
||||||
|
<TableHead>Up-to-date</TableHead>
|
||||||
|
<TableHead>Available</TableHead>
|
||||||
|
<TableHead>Replicas</TableHead>
|
||||||
|
<TableHead>Age</TableHead>
|
||||||
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{deployments.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={7} className="text-center text-muted-foreground">
|
||||||
|
No deployments found
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
deployments.map((deployment) => (
|
||||||
|
<TableRow key={deployment.name}>
|
||||||
|
<TableCell className="font-medium">{deployment.name}</TableCell>
|
||||||
|
<TableCell>{deployment.ready}</TableCell>
|
||||||
|
<TableCell>{deployment.up_to_date}</TableCell>
|
||||||
|
<TableCell>{deployment.available}</TableCell>
|
||||||
|
<TableCell>{deployment.replicas}</TableCell>
|
||||||
|
<TableCell className="text-muted-foreground">{deployment.age}</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
<div className="flex items-center justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setScalingDeployment(deployment)}
|
||||||
|
>
|
||||||
|
<Scale className="w-4 h-4" />
|
||||||
|
Scale
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setRestartingDeployment(deployment)}
|
||||||
|
>
|
||||||
|
<RotateCcw className="w-4 h-4" />
|
||||||
|
Restart
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scale Dialog */}
|
||||||
|
<Dialog open={!!scalingDeployment} onOpenChange={() => setScalingDeployment(null)}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Scale Deployment</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="replicas">Replica Count</Label>
|
||||||
|
<Input
|
||||||
|
id="replicas"
|
||||||
|
type="number"
|
||||||
|
value={replicas}
|
||||||
|
onChange={handleScaleChange}
|
||||||
|
placeholder="Enter replica count"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
{scaleError && (
|
||||||
|
<Alert variant="destructive" className="mt-2">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>{scaleError}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => setScalingDeployment(null)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleScaleSubmit} disabled={isScaling}>
|
||||||
|
{isScaling ? "Scaling..." : "Scale"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Restart Dialog */}
|
||||||
|
<Dialog open={!!restartingDeployment} onOpenChange={() => setRestartingDeployment(null)}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Restart Deployment</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
This will trigger a rolling restart of the deployment.
|
||||||
|
</p>
|
||||||
|
{restartError && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>{restartError}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => setRestartingDeployment(null)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleRestartSubmit} disabled={isRestarting}>
|
||||||
|
{isRestarting ? "Restarting..." : "Restart"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
199
src/components/Kubernetes/PodList.tsx
Normal file
199
src/components/Kubernetes/PodList.tsx
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
|
||||||
|
import { Badge } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui";
|
||||||
|
import { Textarea } from "@/components/ui";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui";
|
||||||
|
import { Terminal, FileText, RotateCcw } from "lucide-react";
|
||||||
|
import { Alert, AlertDescription } from "@/components/ui";
|
||||||
|
import type { PodInfo, LogResponse } from "@/lib/tauriCommands";
|
||||||
|
|
||||||
|
interface PodListProps {
|
||||||
|
pods: PodInfo[];
|
||||||
|
clusterId: string;
|
||||||
|
namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PodList({ pods, clusterId, namespace }: PodListProps) {
|
||||||
|
const [selectedPod, setSelectedPod] = useState<PodInfo | null>(null);
|
||||||
|
const [selectedContainer, setSelectedContainer] = useState<string>("");
|
||||||
|
const [logs, setLogs] = useState<string>("");
|
||||||
|
const [isFetchingLogs, setIsFetchingLogs] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const getPodStatusColor = (status: string) => {
|
||||||
|
switch (status.toLowerCase()) {
|
||||||
|
case "running":
|
||||||
|
return "bg-green-500";
|
||||||
|
case "pending":
|
||||||
|
return "bg-yellow-500";
|
||||||
|
case "succeeded":
|
||||||
|
case "completed":
|
||||||
|
return "bg-blue-500";
|
||||||
|
case "failed":
|
||||||
|
case "error":
|
||||||
|
return "bg-red-500";
|
||||||
|
default:
|
||||||
|
return "bg-gray-500";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchLogs = async () => {
|
||||||
|
if (!selectedPod || !selectedContainer) return;
|
||||||
|
|
||||||
|
setIsFetchingLogs(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const response = await invoke<LogResponse>("get_pod_logs", {
|
||||||
|
clusterId,
|
||||||
|
namespace,
|
||||||
|
podName: selectedPod.name,
|
||||||
|
containerName: selectedContainer,
|
||||||
|
});
|
||||||
|
setLogs(response.logs);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to fetch logs:", err);
|
||||||
|
setError(err instanceof Error ? err.message : "Failed to fetch logs");
|
||||||
|
} finally {
|
||||||
|
setIsFetchingLogs(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContainerChange = (container: string) => {
|
||||||
|
setSelectedContainer(container);
|
||||||
|
setLogs("");
|
||||||
|
setError(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const containers = selectedPod?.containers ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead>Ready</TableHead>
|
||||||
|
<TableHead>Age</TableHead>
|
||||||
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{pods.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={5} className="text-center text-muted-foreground">
|
||||||
|
No pods found
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
pods.map((pod) => (
|
||||||
|
<TableRow key={pod.name}>
|
||||||
|
<TableCell className="font-medium">{pod.name}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge className={`${getPodStatusColor(pod.status)} text-white`}>
|
||||||
|
{pod.status}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{pod.ready}</TableCell>
|
||||||
|
<TableCell className="text-muted-foreground">{pod.age}</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
|
<Button variant="ghost" size="sm" onClick={() => { setSelectedPod(pod); setIsDialogOpen(true); }}>
|
||||||
|
<Terminal className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<DialogContent className="max-w-4xl max-h-[80vh] flex flex-col">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{pod.name} - {namespace} namespace</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex-1 overflow-y-auto flex flex-col">
|
||||||
|
{selectedPod && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium">Container:</span>
|
||||||
|
<select
|
||||||
|
value={selectedContainer}
|
||||||
|
onChange={(e) => handleContainerChange(e.target.value)}
|
||||||
|
className="flex h-9 w-32 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||||
|
>
|
||||||
|
<option value="">Select container...</option>
|
||||||
|
{containers.map((container) => (
|
||||||
|
<option key={container} value={container}>
|
||||||
|
{container}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<Button
|
||||||
|
onClick={fetchLogs}
|
||||||
|
disabled={!selectedContainer || isFetchingLogs}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{isFetchingLogs ? (
|
||||||
|
<>
|
||||||
|
<RotateCcw className="w-4 h-4 animate-spin" />
|
||||||
|
Loading...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FileText className="w-4 h-4" />
|
||||||
|
Fetch Logs
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Tabs value="logs" onValueChange={() => {}}>
|
||||||
|
<TabsList className="grid grid-cols-2">
|
||||||
|
<TabsTrigger value="logs">Logs</TabsTrigger>
|
||||||
|
<TabsTrigger value="details">Details</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<div className="flex-1 overflow-auto">
|
||||||
|
<TabsContent value="logs" className="h-full">
|
||||||
|
<Textarea
|
||||||
|
value={logs}
|
||||||
|
readOnly
|
||||||
|
className="font-mono text-xs h-64"
|
||||||
|
placeholder="No logs available. Click 'Fetch Logs' to retrieve."
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="details" className="h-full">
|
||||||
|
<div className="space-y-2 text-sm">
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="text-muted-foreground">Name:</div>
|
||||||
|
<div>{selectedPod.name}</div>
|
||||||
|
<div className="text-muted-foreground">Status:</div>
|
||||||
|
<div>{selectedPod.status}</div>
|
||||||
|
<div className="text-muted-foreground">Ready:</div>
|
||||||
|
<div>{selectedPod.ready}</div>
|
||||||
|
<div className="text-muted-foreground">Age:</div>
|
||||||
|
<div>{selectedPod.age}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
169
src/components/Kubernetes/ResourceBrowser.tsx
Normal file
169
src/components/Kubernetes/ResourceBrowser.tsx
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
|
import { Card, CardContent, CardHeader } from "@/components/ui";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui";
|
||||||
|
import { Button } from "@/components/ui";
|
||||||
|
import { Loader2, AlertCircle } from "lucide-react";
|
||||||
|
import type { NamespaceInfo, PodInfo, ServiceInfo, DeploymentInfo, StatefulSetInfo, DaemonSetInfo } from "@/lib/tauriCommands";
|
||||||
|
import { listNamespacesCmd, listPodsCmd, listServicesCmd, listDeploymentsCmd, listStatefulsetsCmd, listDaemonsetsCmd } from "@/lib/tauriCommands";
|
||||||
|
import { PodList } from "./PodList";
|
||||||
|
import { ServiceList } from "./ServiceList";
|
||||||
|
import { DeploymentList } from "./DeploymentList";
|
||||||
|
import { StatefulSetList } from "./StatefulSetList";
|
||||||
|
import { DaemonSetList } from "./DaemonSetList";
|
||||||
|
|
||||||
|
type ResourceType = "pods" | "services" | "deployments" | "statefulsets" | "daemonsets";
|
||||||
|
|
||||||
|
interface ResourceBrowserProps {
|
||||||
|
clusterId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ResourceBrowser({ clusterId }: ResourceBrowserProps) {
|
||||||
|
const [namespaces, setNamespaces] = useState<NamespaceInfo[]>([]);
|
||||||
|
const [selectedNamespace, setSelectedNamespace] = useState<string>("all");
|
||||||
|
const [resourceType, setResourceType] = useState<ResourceType>("pods");
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [pods, setPods] = useState<PodInfo[]>([]);
|
||||||
|
const [services, setServices] = useState<ServiceInfo[]>([]);
|
||||||
|
const [deployments, setDeployments] = useState<DeploymentInfo[]>([]);
|
||||||
|
const [statefulsets, setStatefulsets] = useState<StatefulSetInfo[]>([]);
|
||||||
|
const [daemonsets, setDaemonsets] = useState<DaemonSetInfo[]>([]);
|
||||||
|
|
||||||
|
const loadData = useCallback(async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const [namespacesData, podsData, servicesData, deploymentsData, statefulsetsData, daemonsetsData] = await Promise.all([
|
||||||
|
listNamespacesCmd(clusterId),
|
||||||
|
selectedNamespace === "all" ? listPodsCmd(clusterId, "") : listPodsCmd(clusterId, selectedNamespace),
|
||||||
|
selectedNamespace === "all" ? listServicesCmd(clusterId, "") : listServicesCmd(clusterId, selectedNamespace),
|
||||||
|
selectedNamespace === "all" ? listDeploymentsCmd(clusterId, "") : listDeploymentsCmd(clusterId, selectedNamespace),
|
||||||
|
selectedNamespace === "all" ? listStatefulsetsCmd(clusterId, "") : listStatefulsetsCmd(clusterId, selectedNamespace),
|
||||||
|
selectedNamespace === "all" ? listDaemonsetsCmd(clusterId, "") : listDaemonsetsCmd(clusterId, selectedNamespace),
|
||||||
|
]);
|
||||||
|
|
||||||
|
setNamespaces(namespacesData);
|
||||||
|
setPods(podsData);
|
||||||
|
setServices(servicesData);
|
||||||
|
setDeployments(deploymentsData);
|
||||||
|
setStatefulsets(statefulsetsData);
|
||||||
|
setDaemonsets(daemonsetsData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load resources:", err);
|
||||||
|
setError(err instanceof Error ? err.message : "Failed to load resources");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [clusterId, selectedNamespace]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData();
|
||||||
|
}, [loadData, resourceType]);
|
||||||
|
|
||||||
|
const namespaceOptions = useMemo(() => {
|
||||||
|
const options = [{ name: "All Namespaces", value: "all" }];
|
||||||
|
namespaces.forEach(ns => {
|
||||||
|
options.push({ name: ns.name, value: ns.name });
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}, [namespaces]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||||||
|
<p className="text-muted-foreground">Loading Kubernetes resources...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<AlertCircle className="w-12 h-12 text-destructive" />
|
||||||
|
<p className="text-center text-muted-foreground">{error}</p>
|
||||||
|
<Button onClick={loadData}>Retry</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderResourceList = () => {
|
||||||
|
switch (resourceType) {
|
||||||
|
case "pods":
|
||||||
|
return <PodList pods={pods} clusterId={clusterId} namespace={selectedNamespace} />;
|
||||||
|
case "services":
|
||||||
|
return <ServiceList services={services} clusterId={clusterId} namespace={selectedNamespace} />;
|
||||||
|
case "deployments":
|
||||||
|
return <DeploymentList deployments={deployments} clusterId={clusterId} namespace={selectedNamespace} />;
|
||||||
|
case "statefulsets":
|
||||||
|
return <StatefulSetList statefulsets={statefulsets} clusterId={clusterId} namespace={selectedNamespace} />;
|
||||||
|
case "daemonsets":
|
||||||
|
return <DaemonSetList daemonsets={daemonsets} clusterId={clusterId} namespace={selectedNamespace} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full overflow-y-auto p-6 space-y-6">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<h1 className="text-3xl font-bold tracking-tight">Kubernetes Resources</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Browse and manage your Kubernetes resources
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Select value={selectedNamespace} onValueChange={setSelectedNamespace}>
|
||||||
|
<SelectTrigger className="w-[200px]">
|
||||||
|
<SelectValue placeholder="Select namespace" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{namespaceOptions.map((ns) => (
|
||||||
|
<SelectItem key={ns.value} value={ns.value}>
|
||||||
|
{ns.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="flex-1 flex flex-col">
|
||||||
|
<CardHeader>
|
||||||
|
<Tabs value={resourceType} onValueChange={(v) => setResourceType(v as ResourceType)}>
|
||||||
|
<TabsList className="grid grid-cols-5">
|
||||||
|
<TabsTrigger value="pods">Pods</TabsTrigger>
|
||||||
|
<TabsTrigger value="services">Services</TabsTrigger>
|
||||||
|
<TabsTrigger value="deployments">Deployments</TabsTrigger>
|
||||||
|
<TabsTrigger value="statefulsets">StatefulSets</TabsTrigger>
|
||||||
|
<TabsTrigger value="daemonsets">DaemonSets</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</Tabs>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex-1 overflow-hidden">
|
||||||
|
<div className="h-full overflow-y-auto">
|
||||||
|
<TabsContent value={resourceType} className="h-full">
|
||||||
|
{renderResourceList()}
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
80
src/components/Kubernetes/ServiceList.tsx
Normal file
80
src/components/Kubernetes/ServiceList.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
|
||||||
|
import { Badge } from "@/components/ui";
|
||||||
|
import type { ServiceInfo } from "@/lib/tauriCommands";
|
||||||
|
|
||||||
|
interface ServiceListProps {
|
||||||
|
services: ServiceInfo[];
|
||||||
|
clusterId: string;
|
||||||
|
namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ServiceList({ services, clusterId: _clusterId, namespace: _namespace }: ServiceListProps) {
|
||||||
|
const getServiceTypeColor = (type: string) => {
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case "clusterip":
|
||||||
|
return "bg-blue-500";
|
||||||
|
case "nodeport":
|
||||||
|
return "bg-purple-500";
|
||||||
|
case "loadbalancer":
|
||||||
|
return "bg-green-500";
|
||||||
|
case "externalname":
|
||||||
|
return "bg-gray-500";
|
||||||
|
default:
|
||||||
|
return "bg-gray-500";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Type</TableHead>
|
||||||
|
<TableHead>Cluster IP</TableHead>
|
||||||
|
<TableHead>External IP</TableHead>
|
||||||
|
<TableHead>Ports</TableHead>
|
||||||
|
<TableHead>Age</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{services.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={6} className="text-center text-muted-foreground">
|
||||||
|
No services found
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
services.map((service) => (
|
||||||
|
<TableRow key={`${service.name}-${service.namespace}`}>
|
||||||
|
<TableCell className="font-medium">{service.name}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge className={`${getServiceTypeColor(service.type)} text-white`}>
|
||||||
|
{service.type}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm">{service.cluster_ip}</TableCell>
|
||||||
|
<TableCell className="font-mono text-sm">
|
||||||
|
{service.external_ip || "N/A"}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{service.ports.map((port) => (
|
||||||
|
<div key={`${port.port}-${port.protocol}`} className="text-sm">
|
||||||
|
{port.name ? `${port.name}: ` : ""}
|
||||||
|
{port.port}/{port.protocol}
|
||||||
|
{port.target_port && ` → ${port.target_port}`}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-muted-foreground">{service.age}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
44
src/components/Kubernetes/StatefulSetList.tsx
Normal file
44
src/components/Kubernetes/StatefulSetList.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui";
|
||||||
|
import type { StatefulSetInfo } from "@/lib/tauriCommands";
|
||||||
|
|
||||||
|
interface StatefulSetListProps {
|
||||||
|
statefulsets: StatefulSetInfo[];
|
||||||
|
clusterId: string;
|
||||||
|
namespace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatefulSetList({ statefulsets, clusterId: _clusterId, namespace: _namespace }: StatefulSetListProps) {
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Name</TableHead>
|
||||||
|
<TableHead>Ready</TableHead>
|
||||||
|
<TableHead>Replicas</TableHead>
|
||||||
|
<TableHead>Age</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{statefulsets.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={4} className="text-center text-muted-foreground">
|
||||||
|
No statefulsets found
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
statefulsets.map((ss) => (
|
||||||
|
<TableRow key={ss.name}>
|
||||||
|
<TableCell className="font-medium">{ss.name}</TableCell>
|
||||||
|
<TableCell>{ss.ready}</TableCell>
|
||||||
|
<TableCell>{ss.replicas}</TableCell>
|
||||||
|
<TableCell className="text-muted-foreground">{ss.age}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,3 +2,9 @@ export { ClusterList } from "./ClusterList";
|
|||||||
export { PortForwardList } from "./PortForwardList";
|
export { PortForwardList } from "./PortForwardList";
|
||||||
export { AddClusterModal } from "./AddClusterModal";
|
export { AddClusterModal } from "./AddClusterModal";
|
||||||
export { PortForwardForm } from "./PortForwardForm";
|
export { PortForwardForm } from "./PortForwardForm";
|
||||||
|
export { ResourceBrowser } from "./ResourceBrowser";
|
||||||
|
export { PodList } from "./PodList";
|
||||||
|
export { ServiceList } from "./ServiceList";
|
||||||
|
export { DeploymentList } from "./DeploymentList";
|
||||||
|
export { StatefulSetList } from "./StatefulSetList";
|
||||||
|
export { DaemonSetList } from "./DaemonSetList";
|
||||||
|
|||||||
@ -412,4 +412,295 @@ export const RadioGroupItem = React.forwardRef<HTMLInputElement, RadioGroupItemP
|
|||||||
);
|
);
|
||||||
RadioGroupItem.displayName = "RadioGroupItem";
|
RadioGroupItem.displayName = "RadioGroupItem";
|
||||||
|
|
||||||
|
// ─── Table ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export const Table = React.forwardRef<
|
||||||
|
HTMLTableElement,
|
||||||
|
React.HTMLAttributes<HTMLTableElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="relative w-full overflow-auto">
|
||||||
|
<table
|
||||||
|
ref={ref}
|
||||||
|
className={cn("w-full caption-bottom text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
Table.displayName = "Table";
|
||||||
|
|
||||||
|
export const TableHeader = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||||
|
));
|
||||||
|
TableHeader.displayName = "TableHeader";
|
||||||
|
|
||||||
|
export const TableBody = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tbody
|
||||||
|
ref={ref}
|
||||||
|
className={cn("[&_tr:last-child]:border-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableBody.displayName = "TableBody";
|
||||||
|
|
||||||
|
export const TableFooter = React.forwardRef<
|
||||||
|
HTMLTableSectionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableSectionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<tfoot
|
||||||
|
ref={ref}
|
||||||
|
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableFooter.displayName = "TableFooter";
|
||||||
|
|
||||||
|
export const TableRow = React.forwardRef<
|
||||||
|
HTMLTableRowElement,
|
||||||
|
React.HTMLAttributes<HTMLTableRowElement> & { hover?: boolean }
|
||||||
|
>(({ className, hover: _hover, ...props }, ref) => (
|
||||||
|
<tr
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableRow.displayName = "TableRow";
|
||||||
|
|
||||||
|
export const TableHead = React.forwardRef<
|
||||||
|
HTMLTableCellElement,
|
||||||
|
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<th
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableHead.displayName = "TableHead";
|
||||||
|
|
||||||
|
export const TableCell = React.forwardRef<
|
||||||
|
HTMLTableCellElement,
|
||||||
|
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<td
|
||||||
|
ref={ref}
|
||||||
|
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableCell.displayName = "TableCell";
|
||||||
|
|
||||||
|
export const TableCaption = React.forwardRef<
|
||||||
|
HTMLTableCaptionElement,
|
||||||
|
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<caption
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
TableCaption.displayName = "TableCaption";
|
||||||
|
|
||||||
|
// ─── Tabs ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const TabsContext = React.createContext<{
|
||||||
|
value: string;
|
||||||
|
onValueChange: (value: string) => void;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
export function Tabs({ value, onValueChange, children }: { value: string; onValueChange: (value: string) => void; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<TabsContext.Provider value={{ value, onValueChange }}>
|
||||||
|
<div className="w-full">{children}</div>
|
||||||
|
</TabsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TabsList({ className, children }: { className?: string; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TabsTrigger({ className, children, value }: { className?: string; children: React.ReactNode; value: string }) {
|
||||||
|
const ctx = React.useContext(TabsContext);
|
||||||
|
if (!ctx) throw new Error("TabsTrigger must be used within Tabs");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
ctx.value === value
|
||||||
|
? "bg-background text-foreground shadow-sm"
|
||||||
|
: "hover:bg-background hover:text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={() => ctx.onValueChange(value)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TabsContent({ className, children, value }: { className?: string; children: React.ReactNode; value: string }) {
|
||||||
|
const ctx = React.useContext(TabsContext);
|
||||||
|
if (!ctx) throw new Error("TabsContent must be used within Tabs");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
|
ctx.value === value ? "block" : "hidden",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Dialog ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const DialogContext = React.createContext<{
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
export function Dialog({ open, onOpenChange, children }: { open: boolean; onOpenChange: (open: boolean) => void; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<DialogContext.Provider value={{ open, onOpenChange }}>
|
||||||
|
{children}
|
||||||
|
</DialogContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogTrigger({ children }: { children: React.ReactNode }) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogContent({ className, children }: { className?: string; children: React.ReactNode }) {
|
||||||
|
const ctx = React.useContext(DialogContext);
|
||||||
|
if (!ctx) throw new Error("DialogContent must be used within Dialog");
|
||||||
|
|
||||||
|
if (!ctx.open) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogHeader({ className, children }: { className?: string; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogFooter({ className, children }: { className?: string; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogTitle({ className, children }: { className?: string; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<h2 className={cn("text-lg font-semibold leading-none tracking-tight", className)}>
|
||||||
|
{children}
|
||||||
|
</h2>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogDescription({ className, children }: { className?: string; children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<p className={cn("text-sm text-muted-foreground", className)}>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Alert ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border p-4",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface AlertProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof alertVariants> {}
|
||||||
|
|
||||||
|
export function Alert({ className, variant, children, ...props }: AlertProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
role="alert"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AlertTitle({ className, children, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
||||||
|
return (
|
||||||
|
<h5
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</h5>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AlertDescription({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export { cn };
|
export { cn };
|
||||||
|
|||||||
@ -771,6 +771,7 @@ export interface PodInfo {
|
|||||||
status: string;
|
status: string;
|
||||||
ready: string;
|
ready: string;
|
||||||
age: string;
|
age: string;
|
||||||
|
containers: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClusterConnectionState {
|
export interface ClusterConnectionState {
|
||||||
@ -783,6 +784,101 @@ export interface ClusterConnectionStatus {
|
|||||||
context: string;
|
context: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Kubernetes Resource Discovery Types ──────────────────────────────────────
|
||||||
|
|
||||||
|
export interface NamespaceInfo {
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServicePort {
|
||||||
|
name?: string;
|
||||||
|
port: number;
|
||||||
|
target_port?: string;
|
||||||
|
protocol: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
type: string;
|
||||||
|
cluster_ip: string;
|
||||||
|
external_ip?: string;
|
||||||
|
ports: ServicePort[];
|
||||||
|
age: string;
|
||||||
|
selector: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeploymentInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
ready: string;
|
||||||
|
up_to_date: string;
|
||||||
|
available: string;
|
||||||
|
age: string;
|
||||||
|
replicas: number;
|
||||||
|
labels: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatefulSetInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
ready: string;
|
||||||
|
age: string;
|
||||||
|
replicas: number;
|
||||||
|
labels: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DaemonSetInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
desired: number;
|
||||||
|
current: number;
|
||||||
|
ready: number;
|
||||||
|
up_to_date: number;
|
||||||
|
available: number;
|
||||||
|
age: string;
|
||||||
|
labels: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeMetrics {
|
||||||
|
name: string;
|
||||||
|
cpu_usage: string;
|
||||||
|
memory_usage: string;
|
||||||
|
cpu_percentage: number;
|
||||||
|
memory_percentage: number;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PodMetrics {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
cpu_usage: string;
|
||||||
|
memory_usage: string;
|
||||||
|
cpu_percentage: number;
|
||||||
|
memory_percentage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogResponse {
|
||||||
|
logs: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExecResponse {
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
exit_code: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExecSessionResponse {
|
||||||
|
session_id: string;
|
||||||
|
cluster_id: string;
|
||||||
|
namespace: string;
|
||||||
|
pod: string;
|
||||||
|
container?: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Kubernetes Management Commands ───────────────────────────────────────────
|
// ─── Kubernetes Management Commands ───────────────────────────────────────────
|
||||||
|
|
||||||
export const addClusterCmd = (id: string, name: string, kubeconfigContent: string) =>
|
export const addClusterCmd = (id: string, name: string, kubeconfigContent: string) =>
|
||||||
@ -814,3 +910,250 @@ export const testClusterConnectionCmd = (clusterId: string) =>
|
|||||||
|
|
||||||
export const discoverPodsCmd = (clusterId: string, namespace: string) =>
|
export const discoverPodsCmd = (clusterId: string, namespace: string) =>
|
||||||
invoke<PodInfo[]>("discover_pods", { clusterId, namespace });
|
invoke<PodInfo[]>("discover_pods", { clusterId, namespace });
|
||||||
|
|
||||||
|
// ─── Kubernetes Resource Discovery Commands ───────────────────────────────────
|
||||||
|
|
||||||
|
export const listNamespacesCmd = (clusterId: string) =>
|
||||||
|
invoke<NamespaceInfo[]>("list_namespaces", { clusterId });
|
||||||
|
|
||||||
|
export const listPodsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<PodInfo[]>("list_pods", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listServicesCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<ServiceInfo[]>("list_services", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listDeploymentsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<DeploymentInfo[]>("list_deployments", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listStatefulsetsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<StatefulSetInfo[]>("list_statefulsets", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listDaemonsetsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<DaemonSetInfo[]>("list_daemonsets", { clusterId, namespace });
|
||||||
|
|
||||||
|
// ─── Kubernetes Resource Management Commands ──────────────────────────────────
|
||||||
|
|
||||||
|
export const getPodLogsCmd = (clusterId: string, namespace: string, podName: string, containerName: string) =>
|
||||||
|
invoke<LogResponse>("get_pod_logs", { clusterId, namespace, podName, containerName });
|
||||||
|
|
||||||
|
export const scaleDeploymentCmd = (clusterId: string, namespace: string, deploymentName: string, replicas: number) =>
|
||||||
|
invoke<void>("scale_deployment", { clusterId, namespace, deploymentName, replicas });
|
||||||
|
|
||||||
|
export const restartDeploymentCmd = (clusterId: string, namespace: string, deploymentName: string) =>
|
||||||
|
invoke<void>("restart_deployment", { clusterId, namespace, deploymentName });
|
||||||
|
|
||||||
|
export const deleteResourceCmd = (clusterId: string, resourceType: string, namespace: string, resourceName: string) =>
|
||||||
|
invoke<void>("delete_resource", { clusterId, resourceType, namespace, resourceName });
|
||||||
|
|
||||||
|
export const execPodCmd = (clusterId: string, namespace: string, podName: string, containerName: string, command: string, shell?: string) =>
|
||||||
|
invoke<ExecResponse>("exec_pod", { clusterId, namespace, podName, containerName, shell, command });
|
||||||
|
|
||||||
|
// ─── Additional Kubernetes Resource Discovery Types ───────────────────────────
|
||||||
|
|
||||||
|
export interface ReplicaSetInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
replicas: number;
|
||||||
|
ready: string;
|
||||||
|
age: string;
|
||||||
|
labels: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
completions: string;
|
||||||
|
duration: string;
|
||||||
|
age: string;
|
||||||
|
labels: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CronJobInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
schedule: string;
|
||||||
|
active: number;
|
||||||
|
last_schedule: string;
|
||||||
|
age: string;
|
||||||
|
labels: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigMapInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
data_keys: number;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SecretInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
type: string;
|
||||||
|
data_keys: number;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeInfo {
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
roles: string;
|
||||||
|
version: string;
|
||||||
|
internal_ip: string;
|
||||||
|
external_ip?: string;
|
||||||
|
os_image: string;
|
||||||
|
kernel_version: string;
|
||||||
|
kubelet_version: string;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
event_type: string;
|
||||||
|
reason: string;
|
||||||
|
object: string;
|
||||||
|
count: number;
|
||||||
|
first_seen: string;
|
||||||
|
last_seen: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IngressInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
class?: string;
|
||||||
|
host: string;
|
||||||
|
addresses: string[];
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersistentVolumeClaimInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
status: string;
|
||||||
|
volume: string;
|
||||||
|
capacity: string;
|
||||||
|
access_modes: string[];
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PersistentVolumeInfo {
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
capacity: string;
|
||||||
|
access_modes: string[];
|
||||||
|
reclaim_policy: string;
|
||||||
|
storage_class: string;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceAccountInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
secrets: number;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoleInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterRoleInfo {
|
||||||
|
name: string;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoleBindingInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
role: string;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterRoleBindingInfo {
|
||||||
|
name: string;
|
||||||
|
cluster_role: string;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HorizontalPodAutoscalerInfo {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
min_replicas: number;
|
||||||
|
max_replicas: number;
|
||||||
|
current_replicas: number;
|
||||||
|
desired_replicas: number;
|
||||||
|
age: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Additional Kubernetes Resource Discovery Commands ────────────────────────
|
||||||
|
|
||||||
|
export const listReplicasetsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<ReplicaSetInfo[]>("list_replicasets", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listJobsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<JobInfo[]>("list_jobs", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listCronjobsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<CronJobInfo[]>("list_cronjobs", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listConfigmapsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<ConfigMapInfo[]>("list_configmaps", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listSecretsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<SecretInfo[]>("list_secrets", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listNodesCmd = (clusterId: string) =>
|
||||||
|
invoke<NodeInfo[]>("list_nodes", { clusterId });
|
||||||
|
|
||||||
|
export const listEventsCmd = (clusterId: string, namespace?: string) =>
|
||||||
|
invoke<EventInfo[]>("list_events", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listIngressesCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<IngressInfo[]>("list_ingresses", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listPersistentvolumeclaimsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<PersistentVolumeClaimInfo[]>("list_persistentvolumeclaims", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listPersistentvolumesCmd = (clusterId: string) =>
|
||||||
|
invoke<PersistentVolumeInfo[]>("list_persistentvolumes", { clusterId });
|
||||||
|
|
||||||
|
export const listServiceaccountsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<ServiceAccountInfo[]>("list_serviceaccounts", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listRolesCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<RoleInfo[]>("list_roles", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listClusterrolesCmd = (clusterId: string) =>
|
||||||
|
invoke<ClusterRoleInfo[]>("list_clusterroles", { clusterId });
|
||||||
|
|
||||||
|
export const listRolebindingsCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<RoleBindingInfo[]>("list_rolebindings", { clusterId, namespace });
|
||||||
|
|
||||||
|
export const listClusterrolebindingsCmd = (clusterId: string) =>
|
||||||
|
invoke<ClusterRoleBindingInfo[]>("list_clusterrolebindings", { clusterId });
|
||||||
|
|
||||||
|
export const listHorizontalpodautoscalersCmd = (clusterId: string, namespace: string) =>
|
||||||
|
invoke<HorizontalPodAutoscalerInfo[]>("list_horizontalpodautoscalers", { clusterId, namespace });
|
||||||
|
|
||||||
|
// ─── Additional Kubernetes Resource Management Commands ───────────────────────
|
||||||
|
|
||||||
|
export const cordonNodeCmd = (clusterId: string, nodeName: string) =>
|
||||||
|
invoke<void>("cordon_node", { clusterId, nodeName });
|
||||||
|
|
||||||
|
export const uncordonNodeCmd = (clusterId: string, nodeName: string) =>
|
||||||
|
invoke<void>("uncordon_node", { clusterId, nodeName });
|
||||||
|
|
||||||
|
export const drainNodeCmd = (clusterId: string, nodeName: string) =>
|
||||||
|
invoke<void>("drain_node", { clusterId, nodeName });
|
||||||
|
|
||||||
|
export const rollbackDeploymentCmd = (clusterId: string, namespace: string, deploymentName: string) =>
|
||||||
|
invoke<void>("rollback_deployment", { clusterId, namespace, deploymentName });
|
||||||
|
|
||||||
|
export const createResourceCmd = (clusterId: string, namespace: string, resourceType: string, yamlContent: string) =>
|
||||||
|
invoke<void>("create_resource", { clusterId, namespace, resourceType, yamlContent });
|
||||||
|
|
||||||
|
export const editResourceCmd = (clusterId: string, namespace: string, resourceType: string, resourceName: string, yamlContent: string) =>
|
||||||
|
invoke<void>("edit_resource", { clusterId, namespace, resourceType, resourceName, yamlContent });
|
||||||
|
|||||||
@ -103,6 +103,26 @@ export default function LogUpload() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleImagesUpload = useCallback(async (imageFiles: File[]) => {
|
||||||
|
if (!id || imageFiles.length === 0) return;
|
||||||
|
|
||||||
|
setIsUploading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const uploaded = await Promise.all(
|
||||||
|
imageFiles.map(async (file) => {
|
||||||
|
const result = await uploadImageAttachmentCmd(id, file.name);
|
||||||
|
return result;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setImages((prev) => [...prev, ...uploaded]);
|
||||||
|
} catch (err) {
|
||||||
|
setError(String(err));
|
||||||
|
} finally {
|
||||||
|
setIsUploading(false);
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
const handleImageDrop = useCallback(
|
const handleImageDrop = useCallback(
|
||||||
(e: React.DragEvent) => {
|
(e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -113,7 +133,7 @@ export default function LogUpload() {
|
|||||||
handleImagesUpload(imageFiles);
|
handleImagesUpload(imageFiles);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[id]
|
[handleImagesUpload]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleImageFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleImageFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -151,26 +171,6 @@ export default function LogUpload() {
|
|||||||
[id]
|
[id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleImagesUpload = async (imageFiles: File[]) => {
|
|
||||||
if (!id || imageFiles.length === 0) return;
|
|
||||||
|
|
||||||
setIsUploading(true);
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
const uploaded = await Promise.all(
|
|
||||||
imageFiles.map(async (file) => {
|
|
||||||
const result = await uploadImageAttachmentCmd(id, file.name);
|
|
||||||
return result;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setImages((prev) => [...prev, ...uploaded]);
|
|
||||||
} catch (err) {
|
|
||||||
setError(String(err));
|
|
||||||
} finally {
|
|
||||||
setIsUploading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteImage = async (image: ImageAttachment) => {
|
const handleDeleteImage = async (image: ImageAttachment) => {
|
||||||
try {
|
try {
|
||||||
await deleteImageAttachmentCmd(image.id);
|
await deleteImageAttachmentCmd(image.id);
|
||||||
|
|||||||
@ -83,7 +83,7 @@ export default function Triage() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => setError(String(e)));
|
.catch((e) => setError(String(e)));
|
||||||
}, [id]);
|
}, [id, addMessage, setActiveDomain, startSession]);
|
||||||
|
|
||||||
const handleAttach = async () => {
|
const handleAttach = async () => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user