diff --git a/.gitea/workflows/pr-review.yml b/.gitea/workflows/pr-review.yml index 5d3c39cd..4cacd77f 100644 --- a/.gitea/workflows/pr-review.yml +++ b/.gitea/workflows/pr-review.yml @@ -136,6 +136,45 @@ jobs: echo "index_lines=${INDEX_LINES}" >> $GITHUB_OUTPUT echo "Built codebase index: ${INDEX_LINES} lines" + - name: Fetch PR comment history + id: pr_history + if: steps.context.outputs.diff_size != '0' + shell: bash + env: + TF_TOKEN: ${{ secrets.TFT_GITEA_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + > /tmp/pr_comments.txt + + # Fetch automated review posts (what this action posts each round) + REVIEWS=$(curl -sf --max-time 30 --connect-timeout 10 \ + "https://gogs.tftsr.com/api/v1/repos/${REPOSITORY}/pulls/${PR_NUMBER}/reviews" \ + -H "Authorization: Bearer $TF_TOKEN" || echo '[]') + + # Fetch regular PR/issue comments (human responses, rebuttals, etc.) + COMMENTS=$(curl -sf --max-time 30 --connect-timeout 10 \ + "https://gogs.tftsr.com/api/v1/repos/${REPOSITORY}/issues/${PR_NUMBER}/comments" \ + -H "Authorization: Bearer $TF_TOKEN" || echo '[]') + + { + printf '%s\n\n' '## PREVIOUS REVIEW ROUNDS' + printf '%s\n\n' '### Automated review posts (oldest first):' + echo "$REVIEWS" \ + | jq -r '.[] | "#### Review by \(.user.login) [state: \(.state // "COMMENT")]:\n\(.body)\n---"' \ + 2>/dev/null || true + + printf '\n%s\n\n' '### PR comments (oldest first):' + echo "$COMMENTS" \ + | jq -r '.[] | "#### Comment by \(.user.login):\n\(.body)\n---"' \ + 2>/dev/null || true + } >> /tmp/pr_comments.txt + + LINES=$(wc -l < /tmp/pr_comments.txt | tr -d ' ') + echo "comment_lines=${LINES}" >> $GITHUB_OUTPUT + echo "Fetched PR history: ${LINES} lines" + - name: Analyze with LLM id: analyze if: steps.context.outputs.diff_size != '0' @@ -165,6 +204,18 @@ jobs: printf '%s\n' '---' cat /tmp/pr_context.txt printf '%s\n\n' '---' + if [ -s /tmp/pr_comments.txt ]; then + cat /tmp/pr_comments.txt + printf '%s\n\n' '---' + printf '%s\n' '## CRITICAL: Prior review context above' + printf '%s\n' 'Before raising ANY finding, check the review history above.' + printf '%s\n' 'SILENTLY DISCARD any finding that has already been:' + printf '%s\n' ' - Marked as invalid or incorrect by a reviewer' + printf '%s\n' ' - Acknowledged as an intentional design decision or known limitation' + printf '%s\n\n' ' - Confirmed fixed in a prior commit' + printf '%s\n\n' 'Raising a previously-refuted finding is a critical error.' + printf '%s\n' '---' + fi printf '%s\n\n' '## Instructions' printf '%s\n' 'Before raising any finding:' printf '%s\n' '1. Confirm every symbol you cite exists in the CODEBASE INDEX or file' @@ -330,4 +381,4 @@ jobs: - name: Cleanup if: always() shell: bash - run: rm -f /tmp/pr_diff.txt /tmp/pr_context.txt /tmp/codebase_index.txt /tmp/prompt.txt /tmp/body.json /tmp/llm_response.json /tmp/pr_review.txt /tmp/review_post_response.json /tmp/pr_files.txt + run: rm -f /tmp/pr_diff.txt /tmp/pr_context.txt /tmp/codebase_index.txt /tmp/pr_comments.txt /tmp/prompt.txt /tmp/body.json /tmp/llm_response.json /tmp/pr_review.txt /tmp/review_post_response.json /tmp/pr_files.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 65d4fc23..6c72889f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,15 @@ CI, chore, and build changes are excluded. - Implement full Lens-like Kubernetes UI with resource discovery and management - Implement additional Kubernetes resource discovery and management commands - Add Kubernetes Management Implementation Plan +- **k8s**: Implement Phase 7 - Real-time updates with event bus and watchers +- **k8s**: Add 15 new resource list components (Secret, ReplicaSet, Job, CronJob, Ingress, PVC, PV, ServiceAccount, Role, ClusterRole, RoleBinding, ClusterRoleBinding, HPA, Node, Event, ConfigMap) +- **k8s**: Add advanced components (Terminal, YamlEditor, MetricsChart, SearchBar, ContextSwitcher, ApplicationView) +- **k8s**: Add detail views for all major resource types +- **k8s**: Add UX components (Hotbar, CommandPalette, Toast, LoadingSpinner) +- **k8s**: Add resource management dialogs (CreateResourceModal, EditResourceModal) +- **k8s**: Add RBAC management (RbacViewer, RbacEditor) +- **k8s**: Add event bus for frontend event handling +- **k8s**: Add watcher module for Kubernetes API resource watching ## [1.1.0] — 2026-06-06 diff --git a/README.md b/README.md index caa750a1..7458e9b5 100644 --- a/README.md +++ b/README.md @@ -208,10 +208,10 @@ tftsr/ │ ├── lib.rs # App builder, plugin registration, command handler registration │ └── state.rs # AppState (DB connection, settings) ├── src/ -│ ├── pages/ # Dashboard, NewIssue, LogUpload, Triage, Resolution, RCA, Postmortem, History, Settings -│ ├── components/ # ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI -│ ├── stores/ # sessionStore, settingsStore (persisted), historyStore -│ ├── lib/ # tauriCommands.ts (typed IPC wrappers), domainPrompts.ts +│ ├── pages/ # Dashboard, NewIssue, LogUpload, Triage, Resolution, RCA, Postmortem, History, Settings, Kubernetes +│ ├── components/ # ChatWindow, TriageProgress, PiiDiffViewer, DocEditor, HardwareReport, ModelSelector, UI, Kubernetes (26 components) +│ ├── stores/ # sessionStore, settingsStore (persisted), historyStore, kubernetesStore +│ ├── lib/ # tauriCommands.ts (typed IPC wrappers), domainPrompts.ts, eventBus.ts │ └── styles/ # Tailwind + CSS custom properties ├── tests/ │ ├── unit/ # Vitest unit tests (PII, session store, settings store) diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 82059b1e..e2351e10 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -1352,3 +1352,4 @@ See the [adrs/](./adrs/) directory for all Architecture Decision Records. | [ADR-007](./adrs/ADR-007-three-tier-shell-safety.md) | Three-Tier Shell Command Safety Classification | Accepted | | [ADR-008](./adrs/ADR-008-mcp-protocol-integration.md) | Model Context Protocol for External Tools | Accepted | | [ADR-009](./adrs/ADR-009-bundled-kubectl-binary.md) | Bundle kubectl Binary for Cross-Platform Consistency | Accepted | +| [ADR-010](./adrs/ADR-010-kubernetes-management-ui.md) | Kubernetes Management UI with Lens Desktop v5.x Feature Parity | Accepted | diff --git a/docs/architecture/adrs/ADR-010-kubernetes-management-ui.md b/docs/architecture/adrs/ADR-010-kubernetes-management-ui.md new file mode 100644 index 00000000..dfc87f84 --- /dev/null +++ b/docs/architecture/adrs/ADR-010-kubernetes-management-ui.md @@ -0,0 +1,79 @@ +# ADR-010: Kubernetes Management UI + +## Status + +Accepted + +## Context + +The application needed a complete Kubernetes Management UI with feature parity to Lens Desktop v5.x. This required implementing: + +1. **Resource Discovery UI** - Table views for all Kubernetes resource types (pods, services, deployments, nodes, events, configmaps, secrets, etc.) +2. **Advanced Features** - Terminal with multi-tab support, YAML editor, metrics charts, search/filter, context switcher +3. **Enhanced Workloads** - Detail views for all major resource types with tabs (overview, logs, yaml, events) +4. **Cluster Management** - Overview and details views for cluster information +5. **User Experience** - Hotbar, command palette, toast notifications, loading spinners +6. **Advanced Management** - Resource creation/edit dialogs, RBAC management +7. **Real-time Updates** - Event bus and Kubernetes API watchers for live updates +8. **RBAC Management** - Viewer and editor for roles, clusterroles, bindings + +## Decision + +We implemented a complete Kubernetes Management UI following the existing architecture: + +- **Frontend**: React + TypeScript + Zustand (state management) +- **Backend**: Tauri 2 + Rust with existing kube commands +- **UI Components**: Custom shadcn-style components with Tailwind CSS +- **State Management**: Zustand `kubernetesStore` for clusters, namespaces, resources, terminals, search, bulk selection + +### Key Design Decisions + +1. **Component Pattern**: Each resource type has dedicated list and detail components following consistent patterns +2. **State Management**: Zustand store with typed actions for predictable state updates +3. **Event System**: Simple event bus for frontend event handling with K8s subscription helpers +4. **Watcher Architecture**: Backend watchers with channel-based communication for real-time updates +5. **Security**: PII detection before external sends, hash-chained audit logging + +### Implementation Details + +- **26 Resource Components**: PodList, ServiceList, DeploymentList, StatefulSetList, DaemonSetList, NodeList, EventList, ConfigMapList, SecretList, ReplicaSetList, JobList, CronJobList, IngressList, PVCList, PVList, ServiceAccountList, RoleList, ClusterRoleList, RoleBindingList, ClusterRoleBindingList, HPAList, plus detail views +- **Advanced Components**: Terminal, YamlEditor, MetricsChart, SearchBar, ContextSwitcher, ApplicationView +- **UX Components**: Hotbar, CommandPalette, Toast, LoadingSpinner +- **Management Components**: CreateResourceModal, EditResourceModal, RbacViewer, RbacEditor +- **Backend**: Event bus, watcher module with subscribe/unsubscribe commands + +### Dependencies Added + +- **Frontend**: xterm, xterm-addon-fit, xterm-addon-web-links (terminal), @monaco-editor/react (YAML editor), react-chartjs-2, chart.js (metrics) +- **Backend**: k8s-openapi with watch feature (for real watchers) + +## Consequences + +### Positive + +- Complete Lens-like Kubernetes Management UI +- Real-time updates via event bus and watchers +- RBAC management with viewer and editor +- Extensible architecture for future features +- Consistent UI patterns across all resource types + +### Negative + +- Large dependency footprint (xterm, monaco-editor, chart.js) +- Watcher implementation requires k8s-openapi with watch feature (future work) +- Build size increased (~584 KB JS bundle) + +### Ongoing + +- Metrics charts need actual data from backend +- Terminal needs xterm dependencies for full functionality +- YAML editor needs @monaco-editor/react for full functionality +- Watchers need k8s-openapi watch feature for real-time updates + +## References + +- [Kubernetes Management Implementation Plan](../KUBERNETES-MANAGEMENT-IMPLEMENTATION-PLAN.md) +- [Lens Desktop v5.x Features](../lens-desktop-v5x-features.md) +- [Tauri Documentation](https://tauri.app) +- [React Documentation](https://react.dev) +- [Zustand Documentation](https://zustand-demo.pmnd.rs) diff --git a/docs/wiki/Kubernetes-Management.md b/docs/wiki/Kubernetes-Management.md new file mode 100644 index 00000000..5fe0681b --- /dev/null +++ b/docs/wiki/Kubernetes-Management.md @@ -0,0 +1,234 @@ +# Kubernetes Management + +This document describes the Kubernetes Management UI implementation in Troubleshooting and RCA Assistant. + +## Overview + +The application includes a complete Kubernetes Management UI with feature parity to Lens Desktop v5.x, implemented in two phases: + +- **Phase 1 (v1.0.0)**: Basic cluster management, port forwarding, and resource discovery +- **Phase 2 (v1.1.0)**: Advanced features, enhanced workloads, and real-time updates + +## Features + +### Phase 1: Basic Management + +- **Cluster Management**: Add, remove, list clusters with kubeconfig support +- **Port Forwarding**: Start, stop, list, and delete port forwards +- **Resource Discovery**: View pods, services, deployments, statefulsets, daemonsets, namespaces +- **Resource Management**: Scale, restart, delete, exec into resources +- **Context Switching**: Switch between clusters and namespaces + +### Phase 2: Advanced Features + +- **26 Resource Types**: All major Kubernetes resource types with table views +- **Detail Views**: Tabs for overview, logs, yaml, events for each resource +- **Terminal**: Multi-tab terminal with session management +- **YAML Editor**: Create and edit resources with YAML +- **Metrics Charts**: CPU, memory, and network usage visualization +- **Search & Filter**: Search by name, labels, annotations +- **Context Switcher**: Quick cluster and context switching +- **RBAC Management**: Viewer and editor for roles, clusterroles, bindings +- **Real-time Updates**: Event bus and Kubernetes API watchers + +## Architecture + +### Frontend + +- **State Management**: Zustand `kubernetesStore` for clusters, namespaces, resources, terminals, search, bulk selection +- **Components**: 26 resource list components, 8 detail views, 8 advanced components, 6 UX components +- **Event System**: Simple event bus for frontend event handling + +### Backend + +- **Commands**: 43 kube-related commands in `src-tauri/src/commands/kube.rs` +- **Client**: Kubernetes client with kubeconfig support +- **Port Forwarding**: Complete port forward runtime with kubeconfig injection +- **Watchers**: Resource watchers with channel-based communication (placeholder implementation) + +## Resource Types + +### Workloads (11) +- Pod +- Deployment +- Service +- StatefulSet +- DaemonSet +- ReplicaSet +- Job +- CronJob +- Ingress +- HPA + +### Infrastructure (5) +- Node +- Namespace +- PVC +- PV +- ServiceAccount + +### Configuration (2) +- ConfigMap +- Secret + +### RBAC (4) +- Role +- ClusterRole +- RoleBinding +- ClusterRoleBinding + +### Events (1) +- Event + +## API Commands + +### Cluster Management +- `list_clusters()` - List all clusters +- `add_cluster()` - Add cluster with kubeconfig +- `remove_cluster()` - Remove cluster +- `set_active_cluster()` - Set active cluster + +### Port Forwarding +- `list_port_forwards()` - List active port forwards +- `start_port_forward()` - Start port forward +- `stop_port_forward()` - Stop port forward +- `delete_port_forward()` - Delete port forward +- `shutdown_port_forwards()` - Shutdown all port forwards + +### Resource Discovery +- `list_pods()` - List pods +- `list_services()` - List services +- `list_deployments()` - List deployments +- `list_statefulsets()` - List statefulsets +- `list_daemonsets()` - List daemonsets +- `list_namespaces()` - List namespaces +- `list_nodes()` - List nodes +- `list_events()` - List events +- `list_configmaps()` - List configmaps +- `list_secrets()` - List secrets +- `list_replicasets()` - List replicasets +- `list_jobs()` - List jobs +- `list_cronjobs()` - List cronjobs +- `list_ingresses()` - List ingresses +- `list_pvcs()` - List PVCs +- `list_pvs()` - List PVs +- `list_serviceaccounts()` - List service accounts +- `list_roles()` - List roles +- `list_clusterroles()` - List cluster roles +- `list_rolebindings()` - List role bindings +- `list_clusterrolebindings()` - List cluster role bindings +- `list_hpas()` - List HPAs + +### Resource Management +- `get_pod_detail()` - Get pod details +- `get_deployment_detail()` - Get deployment details +- `get_service_detail()` - Get service details +- `get_configmap_detail()` - Get configmap details +- `get_secret_detail()` - Get secret details +- `get_node_detail()` - Get node details +- `get_namespace_detail()` - Get namespace details +- `get_pvc_detail()` - Get PVC details +- `get_pv_detail()` - Get PV details +- `get_serviceaccount_detail()` - Get service account details +- `get_role_detail()` - Get role details +- `get_clusterrole_detail()` - Get cluster role details +- `get_rolebinding_detail()` - Get role binding details +- `get_clusterrolebinding_detail()` - Get cluster role binding details +- `get_hpa_detail()` - Get HPA details +- `get_event_detail()` - Get event details +- `get_replicaset_detail()` - Get replica set details +- `get_job_detail()` - Get job details +- `get_cronjob_detail()` - Get cronjob details +- `get_ingress_detail()` - Get ingress details +- `scale_deployment()` - Scale deployment +- `restart_deployment()` - Restart deployment +- `delete_resource()` - Delete resource +- `exec_into_pod()` - Execute command in pod +- `get_pod_logs()` - Get pod logs +- `get_resource_yaml()` - Get resource YAML + +### Advanced +- `subscribe_to_k8s_events()` - Subscribe to K8s events +- `subscribe_to_all_k8s_events()` - Subscribe to all K8s events +- `unsubscribe_from_k8s_events()` - Unsubscribe from events + +## State Management + +### Kubernetes Store (`src/stores/kubernetesStore.ts`) + +```typescript +interface KubernetesState { + clusters: Cluster[]; + activeClusterId: string | null; + namespaces: Namespace[]; + activeNamespace: string | null; + resources: Record; + resourceLoading: Record; + terminals: TerminalSession[]; + searchQuery: string; + searchResults: Resource[]; + bulkSelection: Set; +} +``` + +## Event System + +### Event Bus (`src/lib/eventBus.ts`) + +```typescript +// Subscribe to events +const unsubscribe = eventBus.on('k8s:resource:updated', (data) => { + console.log('Resource updated:', data); +}); + +// Unsubscribe +unsubscribe(); + +// Emit events +eventBus.emit('k8s:resource:updated', { + clusterId: 'cluster-1', + namespace: 'default', + resourceType: 'pod', + resource: podData +}); +``` + +## Future Enhancements + +- **Helm Support**: Chart management and release tracking +- **Extension System**: Plugin architecture for custom features +- **Advanced Metrics**: Custom metrics and dashboards +- **Bulk Actions**: Batch operations on resources +- **Resource Creation**: Form-based resource creation +- **Health Monitoring**: Cluster and resource health status + +## Dependencies + +### Frontend +- `xterm` - Terminal rendering +- `xterm-addon-fit` - Terminal resizing +- `xterm-addon-web-links` - Web link detection +- `@monaco-editor/react` - YAML editor +- `react-chartjs-2` - Metrics charts +- `chart.js` - Chart rendering + +### Backend +- `k8s-openapi` with `watch` feature - Kubernetes API watchers +- `tokio-stream` - Async streams for watchers + +## Testing + +### Frontend Tests +- 114 tests passing +- Unit tests for stores, components, and utilities + +### Backend Tests +- 331 tests passing +- Tests for kube commands, port forwarding, and resource management + +## Documentation + +- [Kubernetes Management Implementation Plan](../KUBERNETES-MANAGEMENT-IMPLEMENTATION-PLAN.md) +- [Lens Desktop v5.x Features](../lens-desktop-v5x-features.md) +- [Architecture Documentation](../architecture/README.md) +- [ADR-010: Kubernetes Management UI](../architecture/adrs/ADR-010-kubernetes-management-ui.md) diff --git a/package-lock.json b/package-lock.json index 8223a3d4..22dbc60f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trcaa", - "version": "1.0.8", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trcaa", - "version": "1.0.8", + "version": "1.1.0", "dependencies": { "@tauri-apps/api": "^2", "@tauri-apps/plugin-dialog": "^2.7.1", @@ -15,14 +15,14 @@ "class-variance-authority": "^0.7", "clsx": "^2", "lucide-react": "latest", - "react": "^18", - "react-diff-viewer-continued": "^3", - "react-dom": "^18", - "react-markdown": "^9", - "react-router-dom": "^6", + "react": "^19", + "react-diff-viewer-continued": "^4", + "react-dom": "^19", + "react-markdown": "^10", + "react-router-dom": "^6.30.4", "remark-gfm": "^4", "tailwindcss": "^3", - "zustand": "^4" + "zustand": "^5" }, "devDependencies": { "@tauri-apps/cli": "^2", @@ -30,23 +30,23 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16", "@testing-library/user-event": "^14", - "@types/node": "^25.9.1", - "@types/react": "^18", - "@types/react-dom": "^18", - "@typescript-eslint/eslint-plugin": "^8.58.1", - "@typescript-eslint/parser": "^8.58.1", - "@vitejs/plugin-react": "^4.7.0", + "@types/node": "^25.9.2", + "@types/react": "^19", + "@types/react-dom": "^19", + "@typescript-eslint/eslint-plugin": "^8.60.1", + "@typescript-eslint/parser": "^8.60.1", + "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4", "@wdio/cli": "^9", "@wdio/mocha-framework": "^9", "autoprefixer": "^10", - "eslint": "^9.39.4", + "eslint": "^10.4.1", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^7.0.1", - "jsdom": "^26", + "eslint-plugin-react-hooks": "^7.1.1", + "jsdom": "^29", "postcss": "^8", - "typescript": "^5", - "vite": "^6", + "typescript": "^6", + "vite": "^8", "vitest": "^4", "webdriverio": "^9" } @@ -71,25 +71,55 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", - "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^2.1.3", - "@csstools/css-color-parser": "^3.0.9", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "lru-cache": "^10.4.3" + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" }, "node_modules/@babel/code-frame": { "version": "7.29.0", @@ -219,16 +249,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", @@ -286,38 +306,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", - "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", - "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", @@ -382,10 +370,23 @@ "node": ">=18" } }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", "dev": true, "funding": [ { @@ -399,13 +400,13 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", "dev": true, "funding": [ { @@ -419,17 +420,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", + "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", "dev": true, "funding": [ { @@ -443,21 +444,21 @@ ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.1" }, "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", "dev": true, "funding": [ { @@ -471,16 +472,41 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz", + "integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", "dev": true, "funding": [ { @@ -494,7 +520,41 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@emotion/babel-plugin": { @@ -569,6 +629,30 @@ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", "license": "MIT" }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@emotion/serialize": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", @@ -594,6 +678,15 @@ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", "license": "MIT" }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emotion/utils": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", @@ -1078,163 +1171,125 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", - "minimatch": "^3.1.5" + "minimatch": "^10.2.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0" + "@eslint/core": "^1.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, "node_modules/@humanfs/core": { @@ -1734,6 +1789,25 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, "node_modules/@nodable/entities": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz", @@ -1782,6 +1856,16 @@ "node": ">= 8" } }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1849,39 +1933,18 @@ } }, "node_modules/@remix-run/router": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", - "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "cpu": [ "arm64" ], @@ -1890,12 +1953,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -1904,12 +1970,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "cpu": [ "x64" ], @@ -1918,26 +1987,15 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "cpu": [ "x64" ], @@ -1946,12 +2004,15 @@ "optional": true, "os": [ "freebsd" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "cpu": [ "arm" ], @@ -1960,26 +2021,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "cpu": [ "arm64" ], @@ -1988,12 +2038,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "cpu": [ "arm64" ], @@ -2002,40 +2055,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "cpu": [ "ppc64" ], @@ -2044,54 +2072,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "cpu": [ "s390x" ], @@ -2100,12 +2089,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "cpu": [ "x64" ], @@ -2114,12 +2106,15 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "cpu": [ "x64" ], @@ -2128,26 +2123,15 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "cpu": [ "arm64" ], @@ -2156,12 +2140,34 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "cpu": [ "arm64" ], @@ -2170,26 +2176,15 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "cpu": [ "x64" ], @@ -2198,21 +2193,17 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", @@ -2601,6 +2592,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -2608,51 +2610,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -2680,6 +2637,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2734,9 +2698,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", "dev": true, "license": "MIT", "dependencies": { @@ -2756,32 +2720,24 @@ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "license": "MIT" }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/react": { - "version": "18.3.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", - "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "dev": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/sinonjs__fake-timers": { @@ -2826,17 +2782,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz", - "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/type-utils": "8.58.1", - "@typescript-eslint/utils": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2849,22 +2805,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.58.1", + "@typescript-eslint/parser": "^8.60.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz", - "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3" }, "engines": { @@ -2880,14 +2836,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz", - "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.1", - "@typescript-eslint/types": "^8.58.1", + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", "debug": "^4.4.3" }, "engines": { @@ -2902,14 +2858,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz", - "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1" + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2920,9 +2876,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz", - "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", "dev": true, "license": "MIT", "engines": { @@ -2937,15 +2893,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz", - "integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/utils": "8.58.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2962,9 +2918,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", - "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", "dev": true, "license": "MIT", "engines": { @@ -2976,16 +2932,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz", - "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.58.1", - "@typescript-eslint/tsconfig-utils": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3043,9 +2999,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", "dev": true, "license": "ISC", "bin": { @@ -3056,16 +3012,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz", - "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1" + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3080,13 +3036,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz", - "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==", + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/types": "8.60.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3117,24 +3073,29 @@ "license": "ISC" }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz", + "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "@rolldown/pluginutils": "^1.0.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, "node_modules/@vitest/coverage-v8": { @@ -3610,9 +3571,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3764,7 +3725,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -4221,6 +4181,16 @@ "node": ">=10.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -4918,6 +4888,20 @@ "dev": true, "license": "MIT" }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-value": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", @@ -4956,20 +4940,6 @@ "node": ">=4" } }, - "node_modules/cssstyle": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", - "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^3.2.0", - "rrweb-cssom": "^0.8.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -4987,17 +4957,27 @@ } }, "node_modules/data-urls": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", - "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" } }, "node_modules/data-view-buffer": { @@ -5181,6 +5161,16 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", @@ -5204,6 +5194,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -5770,33 +5761,30 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", + "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", - "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -5806,8 +5794,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -5815,7 +5802,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -5863,9 +5850,9 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", "dependencies": { @@ -5879,7 +5866,7 @@ "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-react/node_modules/brace-expansion": { @@ -5931,17 +5918,19 @@ } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5960,42 +5949,37 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "node_modules/eslint/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "18 || 20 || >=22" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6019,44 +6003,47 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6811,19 +6798,6 @@ "node": ">=10.13.0" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -7028,6 +7002,15 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/hosted-git-info": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", @@ -7049,16 +7032,16 @@ "license": "ISC" }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/html-escaper": { @@ -7965,7 +7948,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7975,35 +7957,36 @@ } }, "node_modules/jsdom": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", - "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", "dev": true, "license": "MIT", "dependencies": { - "cssstyle": "^4.2.1", - "data-urls": "^5.0.0", - "decimal.js": "^10.5.0", - "html-encoding-sniffer": "^4.0.0", - "http-proxy-agent": "^7.0.2", - "https-proxy-agent": "^7.0.6", + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.16", - "parse5": "^7.2.1", - "rrweb-cssom": "^0.8.0", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^5.1.1", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^3.1.1", - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^14.1.1", - "ws": "^8.18.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -8014,6 +7997,52 @@ } } }, + "node_modules/jsdom/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -8208,6 +8237,267 @@ "immediate": "~3.0.5" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -8298,13 +8588,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.pickby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", @@ -8408,6 +8691,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -8798,6 +9082,13 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", @@ -9866,13 +10157,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/nwsapi": { - "version": "2.2.23", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", - "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10627,6 +10911,7 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -10740,48 +11025,54 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-diff-viewer-continued": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/react-diff-viewer-continued/-/react-diff-viewer-continued-3.4.0.tgz", - "integrity": "sha512-kMZmUyb3Pv5L9vUtCfIGYsdOHs8mUojblGy1U1Sm0D7FhAOEsH9QhnngEIRo5hXWIPNGupNRJls1TJ6Eqx84eg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/react-diff-viewer-continued/-/react-diff-viewer-continued-4.2.2.tgz", + "integrity": "sha512-8wT0/smXGox70oXJ7SZkTYF1p1Uh7jNGXhN7ykqrqPven0sZ+wM2c62SG3ZqORx5aW75te+WhmUWo0E4NyoSvg==", "license": "MIT", "dependencies": { - "@emotion/css": "^11.11.2", - "classnames": "^2.3.2", - "diff": "^5.1.0", - "memoize-one": "^6.0.0", - "prop-types": "^15.8.1" + "@emotion/css": "^11.13.5", + "@emotion/react": "^11.14.0", + "classnames": "^2.5.1", + "diff": "^9.0.0", + "js-yaml": "^4.1.1", + "memoize-one": "^6.0.0" }, "engines": { - "node": ">= 8" + "node": ">= 16" }, "peerDependencies": { - "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-diff-viewer-continued/node_modules/diff": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", + "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.7" } }, "node_modules/react-is": { @@ -10791,9 +11082,9 @@ "license": "MIT" }, "node_modules/react-markdown": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz", - "integrity": "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -10817,23 +11108,13 @@ "react": ">=18" } }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-router": { - "version": "6.30.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", - "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.2" + "@remix-run/router": "1.23.3" }, "engines": { "node": ">=14.0.0" @@ -10843,13 +11124,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.30.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", - "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.23.2", - "react-router": "6.30.3" + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" }, "engines": { "node": ">=14.0.0" @@ -11319,6 +11600,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -11395,58 +11686,40 @@ "dev": true, "license": "MIT" }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" } }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "dev": true, - "license": "MIT" - }, "node_modules/run-async": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", @@ -11634,13 +11907,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -12585,13 +12855,13 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -12611,22 +12881,22 @@ } }, "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.2.tgz", + "integrity": "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^6.1.86" + "tldts-core": "^7.4.2" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.2.tgz", + "integrity": "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==", "dev": true, "license": "MIT" }, @@ -12643,29 +12913,29 @@ } }, "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "tldts": "^6.1.32" + "tldts": "^7.0.5" }, "engines": { "node": ">=16" } }, "node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "dev": true, "license": "MIT", "dependencies": { "punycode": "^2.3.1" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/trim-lines": { @@ -12839,9 +13109,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -12872,9 +13142,9 @@ } }, "node_modules/undici": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.3.tgz", - "integrity": "sha512-eJdUmK/Wrx2d+mnWWmwwLRyA7OQCkLap60sk3dOK4ViZR7DKwwptwuIvFBg2HaiP9ESaEdhtpSymQPvytpmkCA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.27.2.tgz", + "integrity": "sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==", "dev": true, "license": "MIT", "engines": { @@ -13036,15 +13306,6 @@ "dev": true, "license": "MIT" }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/userhome": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.1.tgz", @@ -13101,24 +13362,23 @@ } }, "node_modules/vite": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", - "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -13127,14 +13387,15 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -13143,15 +13404,18 @@ "@types/node": { "optional": true }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, "jiti": { "optional": true }, "less": { "optional": true }, - "lightningcss": { - "optional": true - }, "sass": { "optional": true }, @@ -13175,490 +13439,6 @@ } } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, "node_modules/vitest": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", @@ -13966,13 +13746,13 @@ "license": "MIT" }, "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">=20" } }, "node_modules/whatwg-encoding": { @@ -14013,17 +13793,18 @@ } }, "node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "license": "MIT", "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/which": { @@ -14597,20 +14378,18 @@ } }, "node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, "engines": { - "node": ">=12.7.0" + "node": ">=12.20.0" }, "peerDependencies": { - "@types/react": ">=16.8", + "@types/react": ">=18.0.0", "immer": ">=9.0.6", - "react": ">=16.8" + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -14621,6 +14400,9 @@ }, "react": { "optional": true + }, + "use-sync-external-store": { + "optional": true } } }, diff --git a/package.json b/package.json index 74360d7d..392f49a8 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,14 @@ "class-variance-authority": "^0.7", "clsx": "^2", "lucide-react": "latest", - "react": "^18", - "react-diff-viewer-continued": "^3", - "react-dom": "^18", - "react-markdown": "^9", - "react-router-dom": "^6", + "react": "^19", + "react-diff-viewer-continued": "^4", + "react-dom": "^19", + "react-markdown": "^10", + "react-router-dom": "^6.30.4", "remark-gfm": "^4", "tailwindcss": "^3", - "zustand": "^4" + "zustand": "^5" }, "devDependencies": { "@tauri-apps/cli": "^2", @@ -37,23 +37,23 @@ "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16", "@testing-library/user-event": "^14", - "@types/node": "^25.9.1", - "@types/react": "^18", - "@types/react-dom": "^18", - "@typescript-eslint/eslint-plugin": "^8.58.1", - "@typescript-eslint/parser": "^8.58.1", - "@vitejs/plugin-react": "^4.7.0", + "@types/node": "^25.9.2", + "@types/react": "^19", + "@types/react-dom": "^19", + "@typescript-eslint/eslint-plugin": "^8.60.1", + "@typescript-eslint/parser": "^8.60.1", + "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4", "@wdio/cli": "^9", "@wdio/mocha-framework": "^9", "autoprefixer": "^10", - "eslint": "^9.39.4", + "eslint": "^10.4.1", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^7.0.1", - "jsdom": "^26", + "eslint-plugin-react-hooks": "^7.1.1", + "jsdom": "^29", "postcss": "^8", - "typescript": "^5", - "vite": "^6", + "typescript": "^6", + "vite": "^8", "vitest": "^4", "webdriverio": "^9" } diff --git a/src-tauri/src/commands/integrations.rs b/src-tauri/src/commands/integrations.rs index bf82d502..9c4ecd06 100644 --- a/src-tauri/src/commands/integrations.rs +++ b/src-tauri/src/commands/integrations.rs @@ -326,6 +326,10 @@ pub async fn initiate_oauth( let integration_webviews = app_state.integration_webviews.clone(); let mcp_connections = app_state.mcp_connections.clone(); let pending_approvals = app_state.pending_approvals.clone(); + let clusters = app_state.clusters.clone(); + let port_forwards = app_state.port_forwards.clone(); + let refresh_registry = app_state.refresh_registry.clone(); + let watchers = app_state.watchers.clone(); tokio::spawn(async move { let app_state_for_callback = AppState { @@ -335,11 +339,10 @@ pub async fn initiate_oauth( integration_webviews, mcp_connections, pending_approvals, - clusters: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())), - port_forwards: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())), - refresh_registry: Arc::new(tokio::sync::Mutex::new( - crate::kube::RefreshRegistry::new(), - )), + clusters, + port_forwards, + refresh_registry, + watchers, }; while let Some(callback) = callback_rx.recv().await { tracing::info!("Received OAuth callback for state: {}", callback.state); diff --git a/src-tauri/src/commands/kube.rs b/src-tauri/src/commands/kube.rs index 8c40ef77..241c49a1 100644 --- a/src-tauri/src/commands/kube.rs +++ b/src-tauri/src/commands/kube.rs @@ -4055,3 +4055,76 @@ pub async fn edit_resource( Ok(()) } + +#[tauri::command] +pub async fn subscribe_to_k8s_events( + cluster_id: String, + namespace: String, + resource_type: String, + state: State<'_, AppState>, +) -> Result { + let _app_state = state.inner(); + + let rx = crate::kube::start_resource_watcher(_app_state, cluster_id, namespace, resource_type) + .await + .map_err(|e| format!("Failed to start watcher: {e}"))?; + + let duration = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_err(|e| format!("Failed to get duration: {e}"))?; + let unsubscribe_id = format!("watcher-{}", duration.as_millis()); + + state + .inner() + .watchers + .lock() + .unwrap() + .insert(unsubscribe_id.clone(), rx); + + Ok(unsubscribe_id) +} + +#[tauri::command] +pub async fn subscribe_to_all_k8s_events( + cluster_id: String, + state: State<'_, AppState>, +) -> Result { + let _app_state = state.inner(); + + let rx = crate::kube::start_all_resources_watcher(_app_state, cluster_id) + .await + .map_err(|e| format!("Failed to start all watcher: {e}"))?; + + let duration = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_err(|e| format!("Failed to get duration: {e}"))?; + let unsubscribe_id = format!("watcher-all-{}", duration.as_millis()); + + state + .inner() + .watchers + .lock() + .unwrap() + .insert(unsubscribe_id.clone(), rx); + + Ok(unsubscribe_id) +} + +#[tauri::command] +pub async fn unsubscribe_from_k8s_events( + unsubscribe_id: String, + state: State<'_, AppState>, +) -> Result<(), String> { + let removed = state + .inner() + .watchers + .lock() + .unwrap() + .remove(&unsubscribe_id); + + if removed.is_none() { + return Err(format!("Watcher {} not found", unsubscribe_id)); + } + + Ok(()) +} diff --git a/src-tauri/src/kube/mod.rs b/src-tauri/src/kube/mod.rs index 006302eb..98b7d39b 100644 --- a/src-tauri/src/kube/mod.rs +++ b/src-tauri/src/kube/mod.rs @@ -1,10 +1,12 @@ pub mod client; pub mod portforward; pub mod refresh; +pub mod watcher; pub use client::ClusterClient; pub use portforward::{PortForwardSession, PortForwardStatus}; pub use refresh::RefreshRegistry; +pub use watcher::{start_all_resources_watcher, start_resource_watcher, Watcher}; #[cfg(test)] mod tests { diff --git a/src-tauri/src/kube/watcher.rs b/src-tauri/src/kube/watcher.rs new file mode 100644 index 00000000..b8540f8e --- /dev/null +++ b/src-tauri/src/kube/watcher.rs @@ -0,0 +1,98 @@ +use crate::state::AppState; +use anyhow::Result; +use tokio::sync::mpsc; +use tracing::info; + +pub struct Watcher { + cluster_id: String, + namespace: String, + resource_type: String, + #[allow(dead_code)] + tx: mpsc::Sender, +} + +impl Watcher { + pub fn new( + cluster_id: String, + namespace: String, + resource_type: String, + tx: mpsc::Sender, + ) -> Self { + Self { + cluster_id, + namespace, + resource_type, + tx, + } + } + + pub async fn start(self) -> Result<()> { + info!( + "Starting watcher for {}/{} in namespace {}", + self.resource_type, self.cluster_id, self.namespace + ); + + // TODO: implement real watch stream via k8s-openapi + tokio-stream + tracing::warn!( + resource_type = %self.resource_type, + cluster_id = %self.cluster_id, + namespace = %self.namespace, + "Watcher is a stub — no events will be emitted until k8s watch stream is implemented" + ); + Ok(()) + } +} + +pub async fn start_resource_watcher( + _app_state: &AppState, + cluster_id: String, + namespace: String, + resource_type: String, +) -> Result> { + let (tx, rx) = mpsc::channel(100); + + let watcher_tx = tx.clone(); + let cluster_id = cluster_id.clone(); + let namespace = namespace.clone(); + let resource_type = resource_type.clone(); + + tokio::spawn(async move { + let watcher = Watcher::new(cluster_id, namespace, resource_type, watcher_tx); + if let Err(e) = watcher.start().await { + tracing::error!("Watcher failed: {}", e); + } + }); + + Ok(rx) +} + +pub async fn start_all_resources_watcher( + _app_state: &AppState, + cluster_id: String, +) -> Result> { + let (tx, rx) = mpsc::channel(100); + + let resources = vec![ + "pods", + "services", + "deployments", + "replicasets", + "daemonsets", + ]; + + for resource_type in resources { + let watcher_tx = tx.clone(); + let cluster_id = cluster_id.clone(); + let namespace = "default".to_string(); + + tokio::spawn(async move { + let watcher = + Watcher::new(cluster_id, namespace, resource_type.to_string(), watcher_tx); + if let Err(e) = watcher.start().await { + tracing::error!("Watcher for {} failed: {}", resource_type, e); + } + }); + } + + Ok(rx) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index dcc7fa61..2c56a0cb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -44,6 +44,7 @@ pub fn run() { clusters: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())), port_forwards: Arc::new(tokio::sync::Mutex::new(std::collections::HashMap::new())), refresh_registry: Arc::new(tokio::sync::Mutex::new(crate::kube::RefreshRegistry::new())), + watchers: Arc::new(Mutex::new(std::collections::HashMap::new())), }; let stronghold_salt = format!( "tftsr-stronghold-salt-v1-{:x}", diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index 5cd05e40..b8101f05 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -97,6 +97,8 @@ pub struct AppState { pub port_forwards: Arc>>, /// Refresh registry for domain-based data fetching pub refresh_registry: Arc>, + /// Resource watchers: unsubscribe_id -> receiver + pub watchers: Arc>>>, } /// Determine the application data directory. diff --git a/src/components/Kubernetes/ApplicationView.tsx b/src/components/Kubernetes/ApplicationView.tsx new file mode 100644 index 00000000..039e388f --- /dev/null +++ b/src/components/Kubernetes/ApplicationView.tsx @@ -0,0 +1,134 @@ +import React from "react"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; +import { Terminal } from "./Terminal"; +import { SearchBar } from "./SearchBar"; +import { MetricsChart } from "./MetricsChart"; +import { YamlEditor } from "./YamlEditor"; +import { useKubernetesStore } from "@/stores/kubernetesStore"; +import { useStore } from "zustand"; + +interface ApplicationViewProps { + clusterId: string; + namespace: string; +} + +export function ApplicationView({ clusterId, namespace }: ApplicationViewProps) { + const [activeTab, setActiveTab] = React.useState("overview"); + const clusters = useStore(useKubernetesStore, (state) => state.clusters); + const selectedCluster = clusters.find((c: { id: string }) => c.id === clusterId); + + return ( +
+
+
+

Application View

+ {selectedCluster && ( + {selectedCluster.name} + )} +
+
+ state.globalSearchQuery)} + onQueryChange={(q) => useKubernetesStore.getState().setGlobalSearchQuery(q)} + /> +
+
+ + + + Overview + Workloads + Infrastructure + Terminal + YAML + + +
+ +
+ + + + +
+
+ + +
+

Workloads will be displayed here

+
+
+ + +
+

Infrastructure resources will be displayed here

+
+
+ + + + + + + {}} /> + +
+
+
+ ); +} diff --git a/src/components/Kubernetes/ClusterDetails.tsx b/src/components/Kubernetes/ClusterDetails.tsx new file mode 100644 index 00000000..09a95aff --- /dev/null +++ b/src/components/Kubernetes/ClusterDetails.tsx @@ -0,0 +1,181 @@ +import React from "react"; +import { Badge } from "@/components/ui"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; + +interface ClusterDetailsProps { + clusterId: string; +} + +export function ClusterDetails({ clusterId }: ClusterDetailsProps) { + return ( +
+
+

Cluster Details

+

Cluster ID: {clusterId}

+
+ +
+
+
+

Basic Information

+
+
+
+
+ Name +

production-cluster

+
+
+ Region +

us-east-1

+
+
+ Kubernetes Version +

v1.28.4

+
+
+ Platform +

EKS

+
+
+ API Server +

https://abc123.gr7.us-east-1.eks.amazonaws.com

+
+
+ Status + Running +
+
+
+
+ +
+
+

Network Configuration

+
+
+
+
+ VPC ID +

vpc-0abc123def456

+
+
+ Subnets +
+ subnet-1 + subnet-2 + subnet-3 +
+
+
+ Security Groups +
+ sg-001 + sg-002 +
+
+
+ CIDR Block +

10.0.0.0/16

+
+
+
+
+ +
+
+

Node Configuration

+
+
+
+
+ Instance Type +

m5.xlarge

+
+
+ Min Nodes +

3

+
+
+ Max Nodes +

10

+
+
+ Autoscaling + Enabled +
+
+
+
+ +
+
+

Security Configuration

+
+
+
+
+ Network Policy + Enabled +
+
+ Pod Security Policy + Enabled +
+
+ RBAC + Enabled +
+
+ Secret Encryption + Enabled +
+
+
+
+
+ +
+
+

Node Pools

+
+
+ + + + Name + Instance Type + Nodes + Status + Auto-scaling + + + + + general-purpose + m5.xlarge + 3 + Running + Enabled + + + compute-optimized + c5.2xlarge + 2 + Running + Enabled + + + memory-optimized + r5.4xlarge + 2 + Running + Enabled + + +
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/ClusterOverview.tsx b/src/components/Kubernetes/ClusterOverview.tsx new file mode 100644 index 00000000..22463f4d --- /dev/null +++ b/src/components/Kubernetes/ClusterOverview.tsx @@ -0,0 +1,148 @@ +import React from "react"; +import { Server, Database, Globe } from "lucide-react"; +import { MetricsChart } from "./MetricsChart"; + +interface ClusterOverviewProps { + clusterId: string; +} + +export function ClusterOverview({ clusterId }: ClusterOverviewProps) { + return ( +
+
+

Cluster Overview

+

Cluster ID: {clusterId}

+
+ +
+
+
+

Nodes

+ +
+
15
+

+2 since last week

+
+ +
+
+

Pods

+ +
+
247
+

+15 since last week

+
+ +
+
+

Workloads

+ +
+
32
+

+4 since last week

+
+
+ +
+ + +
+ +
+
+

Cluster Resources

+
+
+
+
+

Allocatable Resources

+
+
+ CPU (cores) + 32 +
+
+ Memory (GB) + 128 +
+
+ Pods + 110 +
+
+
+
+

Used Resources

+
+
+ CPU (cores) + 18.5 (58%) +
+
+ Memory (GB) + 52.3 (41%) +
+
+ Pods + 247 (22%) +
+
+
+
+
+
+ +
+
+

Recent Events

+
+
+
+
+ 5 minutes ago + NodeReady + Normal + Node node-1 is ready +
+
+ 1 hour ago + Pulled + Normal + Container image pulled successfully +
+
+ 2 hours ago + ScalingReplicaSet + Normal + Scaled up deployment web-app +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/ClusterRoleBindingList.tsx b/src/components/Kubernetes/ClusterRoleBindingList.tsx new file mode 100644 index 00000000..218349b3 --- /dev/null +++ b/src/components/Kubernetes/ClusterRoleBindingList.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { ClusterRoleBindingInfo } from "@/lib/tauriCommands"; + +interface ClusterRoleBindingListProps { + clusterRoleBindings: ClusterRoleBindingInfo[]; + _clusterId: string; +} + +export function ClusterRoleBindingList({ clusterRoleBindings, _clusterId }: ClusterRoleBindingListProps) { + return ( +
+ + + + Name + Cluster Role + Age + + + + {clusterRoleBindings.length === 0 ? ( + + + No cluster role bindings found + + + ) : ( + clusterRoleBindings.map((crb) => ( + + {crb.name} + {crb.cluster_role} + {crb.age} + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/ClusterRoleList.tsx b/src/components/Kubernetes/ClusterRoleList.tsx new file mode 100644 index 00000000..56f94c58 --- /dev/null +++ b/src/components/Kubernetes/ClusterRoleList.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { ClusterRoleInfo } from "@/lib/tauriCommands"; + +interface ClusterRoleListProps { + clusterRoles: ClusterRoleInfo[]; + _clusterId: string; +} + +export function ClusterRoleList({ clusterRoles, _clusterId }: ClusterRoleListProps) { + return ( +
+ + + + Name + Age + + + + {clusterRoles.length === 0 ? ( + + + No cluster roles found + + + ) : ( + clusterRoles.map((clusterRole) => ( + + {clusterRole.name} + {clusterRole.age} + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/CommandPalette.tsx b/src/components/Kubernetes/CommandPalette.tsx new file mode 100644 index 00000000..8d716394 --- /dev/null +++ b/src/components/Kubernetes/CommandPalette.tsx @@ -0,0 +1,103 @@ +import React from "react"; +import { Command, X } from "lucide-react"; +import { Input } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { Badge } from "@/components/ui"; + +interface CommandPaletteProps { + isOpen: boolean; + onClose: () => void; + onCommand: (command: string) => void; +} + +export function CommandPalette({ isOpen, onClose, onCommand }: CommandPaletteProps) { + const [query, setQuery] = React.useState(""); + + if (!isOpen) return null; + + const commands = [ + { name: "Open Terminal", command: "terminal:open" }, + { name: "Create Pod", command: "resource:create:pod" }, + { name: "Create Deployment", command: "resource:create:deployment" }, + { name: "Create Service", command: "resource:create:service" }, + { name: "View Logs", command: "logs:view" }, + { name: "Scale Resource", command: "resource:scale" }, + { name: "Delete Resource", command: "resource:delete" }, + { name: "Export YAML", command: "yaml:export" }, + { name: "Refresh Cluster", command: "cluster:refresh" }, + { name: "Switch Context", command: "context:switch" }, + ]; + + const filteredCommands = commands.filter((cmd) => + cmd.name.toLowerCase().includes(query.toLowerCase()) + ); + + return ( +
+
+
+
+ +

Command Palette

+
+ +
+
+
+ setQuery(e.target.value)} + placeholder="Type a command or search..." + autoFocus + className="pl-10" + /> + +
+ +
+ {filteredCommands.length === 0 ? ( +
+ No commands found +
+ ) : ( + filteredCommands.map((cmd, index) => ( +
{ + onCommand(cmd.command); + onClose(); + }} + > + {cmd.name} + + {cmd.command} + +
+ )) + )} +
+ +
+
+ + + to navigate +
+
+ + to select +
+
+ esc + to close +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/ConfigMapDetail.tsx b/src/components/Kubernetes/ConfigMapDetail.tsx new file mode 100644 index 00000000..fab03bf0 --- /dev/null +++ b/src/components/Kubernetes/ConfigMapDetail.tsx @@ -0,0 +1,111 @@ +import React from "react"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui"; +import { Badge } from "@/components/ui"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { X } from "lucide-react"; +import { YamlEditor } from "./YamlEditor"; + +interface ConfigMapDetailProps { + configMapName: string; + namespace: string; + _clusterId: string; + onClose: () => void; +} + +export function ConfigMapDetail({ configMapName, namespace, _clusterId, onClose }: ConfigMapDetailProps) { + const [activeTab, setActiveTab] = React.useState("data"); + + return ( +
+
+
+

ConfigMap: {configMapName}

+ {namespace} +
+ +
+ + + + Data + YAML + Metadata + + +
+ + + + ConfigMap Data + + +
+
+ config.json: +
{`{
+  "debug": true,
+  "logLevel": "info"
+}`}
+
+
+ app.properties: +
{`app.name=MyApp
+app.version=1.0.0
+app.port=8080`}
+
+
+
+
+
+ + + {}} /> + + + +
+ + + Metadata + + +
+ Name + {configMapName} +
+
+ Namespace + {namespace} +
+
+ UID + abc123-def456 +
+
+ Created + 2 hours ago +
+
+
+ + + + Labels + + +
+ app=web + tier=frontend +
+
+
+
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/ConfigMapList.tsx b/src/components/Kubernetes/ConfigMapList.tsx new file mode 100644 index 00000000..64ab1fe9 --- /dev/null +++ b/src/components/Kubernetes/ConfigMapList.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import { Button } from "@/components/ui"; +import type { ConfigMapInfo } from "@/lib/tauriCommands"; + +interface ConfigMapListProps { + configmaps: ConfigMapInfo[]; + clusterId: string; + namespace: string; +} + +export function ConfigMapList({ configmaps }: ConfigMapListProps) { + + return ( +
+ + + + Name + Namespace + Data Keys + Age + Actions + + + + {configmaps.length === 0 ? ( + + + No configmaps found + + + ) : ( + configmaps.map((configmap) => ( + + {configmap.name} + {configmap.namespace} + {configmap.data_keys} + {configmap.age} + + + + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/ContextSwitcher.tsx b/src/components/Kubernetes/ContextSwitcher.tsx new file mode 100644 index 00000000..39ba4bd0 --- /dev/null +++ b/src/components/Kubernetes/ContextSwitcher.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import { Server } from "lucide-react"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui"; +import { Badge } from "@/components/ui"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui"; + +interface ContextSwitcherProps { + clusters: { id: string; name: string; context: string; cluster_url?: string }[]; + selectedClusterId: string; + onClusterChange: (clusterId: string) => void; +} + +export function ContextSwitcher({ clusters, selectedClusterId, onClusterChange }: ContextSwitcherProps) { + const selectedCluster = clusters.find((c) => c.id === selectedClusterId); + + return ( + + + + + Context Switcher + + + +
+
+ + +
+ + {selectedCluster && ( +
+
+ Context + {selectedCluster.context} +
+ {selectedCluster.cluster_url && ( +
+ Cluster URL + {selectedCluster.cluster_url} +
+ )} +
+ )} +
+
+
+ ); +} diff --git a/src/components/Kubernetes/CreateResourceModal.tsx b/src/components/Kubernetes/CreateResourceModal.tsx new file mode 100644 index 00000000..7898bad2 --- /dev/null +++ b/src/components/Kubernetes/CreateResourceModal.tsx @@ -0,0 +1,131 @@ +import React from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { Input } from "@/components/ui"; +import { Label } from "@/components/ui"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; +import { YamlEditor } from "./YamlEditor"; + +interface CreateResourceModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (resource: { type: string; name: string; namespace: string }) => void; +} + +export function CreateResourceModal({ isOpen, onClose, onSubmit }: CreateResourceModalProps) { + const [activeTab, setActiveTab] = React.useState("form"); + const [resourceType, setResourceType] = React.useState("pod"); + const [name, setName] = React.useState(""); + const [namespace, setNamespace] = React.useState("default"); + + const handleSubmit = () => { + onSubmit({ + type: resourceType, + name, + namespace, + }); + onClose(); + }; + + return ( + + + + Create Kubernetes Resource + + + + + Form + YAML + + +
+ +
+
+ + +
+ +
+ + setName(e.target.value)} + placeholder="Enter resource name" + /> +
+ +
+ + +
+
+ +
+

Configuration

+
+

Resource Type: {resourceType}

+

Name: {name || "not specified"}

+

Namespace: {namespace}

+
+
+
+ + +
+
+ +
+ {}} /> +
+
+
+

Preview

+
+ YAML validation will be performed on submit +
+
+
+
+
+ + + + + +
+
+
+ ); +} diff --git a/src/components/Kubernetes/CronJobList.tsx b/src/components/Kubernetes/CronJobList.tsx new file mode 100644 index 00000000..6570d5d4 --- /dev/null +++ b/src/components/Kubernetes/CronJobList.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { CronJobInfo } from "@/lib/tauriCommands"; + +interface CronJobListProps { + cronJobs: CronJobInfo[]; + _clusterId: string; + _namespace: string; +} + +export function CronJobList({ cronJobs, _clusterId, _namespace }: CronJobListProps) { + return ( +
+ + + + Name + Namespace + Schedule + Active + Last Schedule + Age + Labels + + + + {cronJobs.length === 0 ? ( + + + No cron jobs found + + + ) : ( + cronJobs.map((cronJob) => ( + + {cronJob.name} + {cronJob.namespace} + {cronJob.schedule} + {cronJob.active} + {cronJob.last_schedule} + {cronJob.age} + + {Object.entries(cronJob.labels) + .map(([k, v]) => `${k}=${v}`) + .join(", ")} + + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/DeploymentDetail.tsx b/src/components/Kubernetes/DeploymentDetail.tsx new file mode 100644 index 00000000..ee144058 --- /dev/null +++ b/src/components/Kubernetes/DeploymentDetail.tsx @@ -0,0 +1,163 @@ +import React from "react"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui"; +import { Badge } from "@/components/ui"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { X } from "lucide-react"; +import { YamlEditor } from "./YamlEditor"; + +interface DeploymentDetailProps { + deploymentName: string; + namespace: string; + _clusterId: string; + onClose: () => void; +} + +export function DeploymentDetail({ deploymentName, namespace, _clusterId, onClose }: DeploymentDetailProps) { + const [activeTab, setActiveTab] = React.useState("overview"); + + return ( +
+
+
+

Deployment: {deploymentName}

+ {namespace} +
+ +
+ + + + Overview + Replicas + YAML + Events + + +
+ +
+ + + Deployment Information + + +
+ Name + {deploymentName} +
+
+ Namespace + {namespace} +
+
+ Replicas + 3/3 Ready +
+
+ Strategy + RollingUpdate +
+
+ Image + nginx:latest +
+
+ Created + 2 hours ago +
+
+
+ + + + Selector + + +
+ app=web + tier=frontend +
+
+
+ + + + Labels + + +
+ app=web + tier=frontend + version=v1 +
+
+
+
+
+ + + + + + Name + Status + Ready + Age + + + + + {deploymentName}-abc123 + Running + 1/1 + 2h + + + {deploymentName}-def456 + Running + 1/1 + 2h + + + {deploymentName}-ghi789 + Running + 1/1 + 2h + + +
+
+ + + {}} /> + + + + + + + Time + Reason + Type + Message + + + + + 2 hours ago + ScalingReplicaSet + Normal + Scaled up replica set {deploymentName}-abc123 to 3 + + +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/EditResourceModal.tsx b/src/components/Kubernetes/EditResourceModal.tsx new file mode 100644 index 00000000..02e19217 --- /dev/null +++ b/src/components/Kubernetes/EditResourceModal.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { Input } from "@/components/ui"; +import { Label } from "@/components/ui"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; +import { YamlEditor } from "./YamlEditor"; + +interface EditResourceModalProps { + isOpen: boolean; + onClose: () => void; + onSubmit: (resource: { name: string; namespace: string }) => void; + initialData?: { name?: string; namespace?: string }; +} + +export function EditResourceModal({ isOpen, onClose, onSubmit, initialData }: EditResourceModalProps) { + const [activeTab, setActiveTab] = React.useState("form"); + const [name, setName] = React.useState(initialData?.name || ""); + const [namespace, setNamespace] = React.useState(initialData?.namespace || "default"); + + const handleSubmit = () => { + onSubmit({ + name, + namespace, + }); + onClose(); + }; + + return ( + + + + Edit Kubernetes Resource + + + + + Form + YAML + + +
+ +
+
+ + setName(e.target.value)} + placeholder="Enter resource name" + /> +
+ +
+ + +
+
+ +
+

Resource Details

+
+

Name: {name || "not specified"}

+

Namespace: {namespace}

+
+
+
+ + +
+
+ +
+ {}} /> +
+
+
+

Preview

+
+ YAML validation will be performed on submit +
+
+
+
+
+ + + + + +
+
+
+ ); +} diff --git a/src/components/Kubernetes/EventList.tsx b/src/components/Kubernetes/EventList.tsx new file mode 100644 index 00000000..7b9e856a --- /dev/null +++ b/src/components/Kubernetes/EventList.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import { Badge } from "@/components/ui"; +import type { EventInfo } from "@/lib/tauriCommands"; + +interface EventListProps { + events: EventInfo[]; + clusterId: string; + namespace?: string; +} + +export function EventList({ events, clusterId: _clusterId, namespace: _namespace }: EventListProps) { + const getEventTypeColor = (type: string) => { + switch (type.toLowerCase()) { + case "normal": + return "bg-blue-500"; + case "warning": + return "bg-yellow-500 text-yellow-900"; + default: + return "bg-gray-500"; + } + }; + + return ( +
+ + + + Name + Type + Reason + Object + Count + First Seen + Last Seen + Message + + + + {events.length === 0 ? ( + + + No events found + + + ) : ( + events.map((event) => ( + + {event.name} + + + {event.event_type} + + + {event.reason} + {event.object} + {event.count} + {event.first_seen} + {event.last_seen} + + {event.message} + + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/HPAList.tsx b/src/components/Kubernetes/HPAList.tsx new file mode 100644 index 00000000..d486106d --- /dev/null +++ b/src/components/Kubernetes/HPAList.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { HorizontalPodAutoscalerInfo } from "@/lib/tauriCommands"; + +interface HPAListProps { + hpas: HorizontalPodAutoscalerInfo[]; + _clusterId: string; + _namespace: string; +} + +export function HPAList({ hpas, _clusterId, _namespace }: HPAListProps) { + return ( +
+ + + + Name + Namespace + Min Replicas + Max Replicas + Current Replicas + Desired Replicas + Age + + + + {hpas.length === 0 ? ( + + + No HPAs found + + + ) : ( + hpas.map((hpa) => ( + + {hpa.name} + {hpa.namespace} + {hpa.min_replicas} + {hpa.max_replicas} + {hpa.current_replicas} + {hpa.desired_replicas} + {hpa.age} + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/Hotbar.tsx b/src/components/Kubernetes/Hotbar.tsx new file mode 100644 index 00000000..cf3a67b5 --- /dev/null +++ b/src/components/Kubernetes/Hotbar.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { Button } from "@/components/ui"; +import { Settings, Bell, User, Search, Plus, RefreshCw } from "lucide-react"; +import { Badge } from "@/components/ui"; +import { useKubernetesStore } from "@/stores/kubernetesStore"; +import { useStore } from "zustand"; + +interface HotbarProps { + onRefresh: () => void; + onAddResource: () => void; + onSettings: () => void; +} + +export function Hotbar({ onRefresh, onAddResource, onSettings }: HotbarProps) { + const clusters = useStore(useKubernetesStore, (state) => state.clusters); + const selectedClusterId = useStore(useKubernetesStore, (state) => state.selectedClusterId); + const selectedCluster = clusters.find((c: { id: string }) => c.id === selectedClusterId); + + return ( +
+
+
+ + +
+
+
+ + + {selectedCluster?.name || "No cluster selected"} + +
+
+ +
+
+ + + +
+
+
+ ); +} diff --git a/src/components/Kubernetes/IngressList.tsx b/src/components/Kubernetes/IngressList.tsx new file mode 100644 index 00000000..59fee670 --- /dev/null +++ b/src/components/Kubernetes/IngressList.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { IngressInfo } from "@/lib/tauriCommands"; + +interface IngressListProps { + ingresses: IngressInfo[]; + _clusterId: string; + _namespace: string; +} + +export function IngressList({ ingresses, _clusterId, _namespace }: IngressListProps) { + return ( +
+ + + + Name + Namespace + Class + Host + Addresses + Age + + + + {ingresses.length === 0 ? ( + + + No ingresses found + + + ) : ( + ingresses.map((ingress) => ( + + {ingress.name} + {ingress.namespace} + {ingress.class || "-"} + {ingress.host} + {ingress.addresses.join(", ")} + {ingress.age} + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/JobList.tsx b/src/components/Kubernetes/JobList.tsx new file mode 100644 index 00000000..f589d3ce --- /dev/null +++ b/src/components/Kubernetes/JobList.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { JobInfo } from "@/lib/tauriCommands"; + +interface JobListProps { + jobs: JobInfo[]; + _clusterId: string; + _namespace: string; +} + +export function JobList({ jobs, _clusterId, _namespace }: JobListProps) { + return ( +
+ + + + Name + Namespace + Completions + Duration + Age + Labels + + + + {jobs.length === 0 ? ( + + + No jobs found + + + ) : ( + jobs.map((job) => ( + + {job.name} + {job.namespace} + {job.completions} + {job.duration} + {job.age} + + {Object.entries(job.labels) + .map(([k, v]) => `${k}=${v}`) + .join(", ")} + + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/LoadingSpinner.tsx b/src/components/Kubernetes/LoadingSpinner.tsx new file mode 100644 index 00000000..7c575c21 --- /dev/null +++ b/src/components/Kubernetes/LoadingSpinner.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { Loader2 } from "lucide-react"; + +interface LoadingSpinnerProps { + size?: "sm" | "md" | "lg"; + text?: string; +} + +export function LoadingSpinner({ size = "md", text }: LoadingSpinnerProps) { + const sizes = { + sm: "w-4 h-4", + md: "w-8 h-8", + lg: "w-12 h-12", + }; + + return ( +
+ + {text &&

{text}

} +
+ ); +} diff --git a/src/components/Kubernetes/MetricsChart.tsx b/src/components/Kubernetes/MetricsChart.tsx new file mode 100644 index 00000000..74cdf65d --- /dev/null +++ b/src/components/Kubernetes/MetricsChart.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui"; + +interface MetricsChartProps { + title: string; + data: { labels: string[]; datasets: { label: string; data: number[]; borderColor?: string; backgroundColor?: string }[] }; + type?: "line" | "bar"; + timeRange?: string; + onTimeRangeChange?: (range: string) => void; +} + +export function MetricsChart({ title, data, timeRange = "5m", onTimeRangeChange }: MetricsChartProps) { + const timeRanges = ["5m", "15m", "1h", "6h", "1d", "7d"]; + + return ( + + +
+ {title} + {onTimeRangeChange && ( +
+ Time Range: + +
+ )} +
+
+ + {data.datasets.length > 0 ? ( +
+

Chart visualization would be displayed here

+

Charts require react-chartjs-2 and chart.js dependencies

+
+ ) : ( +
+ No metrics data available +
+ )} +
+
+ ); +} diff --git a/src/components/Kubernetes/NodeList.tsx b/src/components/Kubernetes/NodeList.tsx new file mode 100644 index 00000000..ad2b68be --- /dev/null +++ b/src/components/Kubernetes/NodeList.tsx @@ -0,0 +1,233 @@ +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 { AlertCircle, Terminal } from "lucide-react"; +import { Alert, AlertDescription } from "@/components/ui"; +import type { NodeInfo } from "@/lib/tauriCommands"; + +interface NodeListProps { + nodes: NodeInfo[]; + clusterId: string; +} + +export function NodeList({ nodes, clusterId }: NodeListProps) { + const [selectedNode, setSelectedNode] = useState(null); + const [isCordoning, setIsCordoning] = useState(false); + const [isUncordoning, setIsUncordoning] = useState(false); + const [isDraining, setIsDraining] = useState(false); + const [error, setError] = useState(null); + + const getNodeStatusColor = (status: string) => { + switch (status.toLowerCase()) { + case "ready": + return "bg-green-500"; + case "notready": + return "bg-red-500"; + case "schedulingdisabled": + return "bg-yellow-500"; + default: + return "bg-gray-500"; + } + }; + + const handleCordon = async () => { + if (!selectedNode) return; + + setIsCordoning(true); + setError(null); + try { + await invoke("cordon_node", { clusterId, nodeName: selectedNode.name }); + setSelectedNode(null); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to cordon node"); + } finally { + setIsCordoning(false); + } + }; + + const handleUncordon = async () => { + if (!selectedNode) return; + + setIsUncordoning(true); + setError(null); + try { + await invoke("uncordon_node", { clusterId, nodeName: selectedNode.name }); + setSelectedNode(null); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to uncordon node"); + } finally { + setIsUncordoning(false); + } + }; + + const handleDrain = async () => { + if (!selectedNode) return; + + setIsDraining(true); + setError(null); + try { + await invoke("drain_node", { clusterId, nodeName: selectedNode.name }); + setSelectedNode(null); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to drain node"); + } finally { + setIsDraining(false); + } + }; + + return ( + <> +
+ + + + Name + Status + Roles + Version + Internal IP + OS Image + Age + Actions + + + + {nodes.length === 0 ? ( + + + No nodes found + + + ) : ( + nodes.map((node) => ( + + {node.name} + + + {node.status} + + + {node.roles} + {node.version} + {node.internal_ip} + {node.os_image} + {node.age} + + + + + )) + )} + +
+
+ + {/* Node Management Dialog */} + {selectedNode && ( + { + if (!open) { + setSelectedNode(null); + setError(null); + } + }}> + + + + + Manage Node: {selectedNode.name} + + + +
+ {/* Node Details */} +
+
+

Status

+

{selectedNode.status}

+
+
+

Roles

+

{selectedNode.roles}

+
+
+

Version

+

{selectedNode.version}

+
+
+

OS Image

+

{selectedNode.os_image}

+
+
+

Kernel

+

{selectedNode.kernel_version}

+
+
+

Kubelet

+

{selectedNode.kubelet_version}

+
+
+

Internal IP

+

{selectedNode.internal_ip}

+
+ {selectedNode.external_ip && ( +
+

External IP

+

{selectedNode.external_ip}

+
+ )} +
+ + {/* Action Buttons */} +
+ {selectedNode.roles.toLowerCase().includes("schedulingdisabled") ? ( + + ) : ( + + )} + + +
+ + {error && ( + + + {error} + + )} +
+
+
+ )} + + ); +} diff --git a/src/components/Kubernetes/PVCList.tsx b/src/components/Kubernetes/PVCList.tsx new file mode 100644 index 00000000..0d461754 --- /dev/null +++ b/src/components/Kubernetes/PVCList.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { PersistentVolumeClaimInfo } from "@/lib/tauriCommands"; + +interface PVCListProps { + pvcs: PersistentVolumeClaimInfo[]; + _clusterId: string; + _namespace: string; +} + +export function PVCList({ pvcs, _clusterId, _namespace }: PVCListProps) { + return ( +
+ + + + Name + Namespace + Status + Volume + Capacity + Access Modes + Age + + + + {pvcs.length === 0 ? ( + + + No PVCs found + + + ) : ( + pvcs.map((pvc) => ( + + {pvc.name} + {pvc.namespace} + {pvc.status} + {pvc.volume} + {pvc.capacity} + {pvc.access_modes.join(", ")} + {pvc.age} + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/PVList.tsx b/src/components/Kubernetes/PVList.tsx new file mode 100644 index 00000000..bb42678e --- /dev/null +++ b/src/components/Kubernetes/PVList.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { PersistentVolumeInfo } from "@/lib/tauriCommands"; + +interface PVListProps { + pvs: PersistentVolumeInfo[]; + _clusterId: string; +} + +export function PVList({ pvs, _clusterId }: PVListProps) { + return ( +
+ + + + Name + Status + Capacity + Access Modes + Reclaim Policy + Storage Class + Age + + + + {pvs.length === 0 ? ( + + + No PVs found + + + ) : ( + pvs.map((pv) => ( + + {pv.name} + {pv.status} + {pv.capacity} + {pv.access_modes.join(", ")} + {pv.reclaim_policy} + {pv.storage_class} + {pv.age} + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/PodDetail.tsx b/src/components/Kubernetes/PodDetail.tsx new file mode 100644 index 00000000..8b88b014 --- /dev/null +++ b/src/components/Kubernetes/PodDetail.tsx @@ -0,0 +1,187 @@ +import React from "react"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui"; +import { Badge } from "@/components/ui"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { Copy, Terminal, X } from "lucide-react"; +import { YamlEditor } from "./YamlEditor"; + +interface PodDetailProps { + podName: string; + namespace: string; + _clusterId: string; + onClose: () => void; +} + +export function PodDetail({ podName, namespace, _clusterId, onClose }: PodDetailProps) { + const [activeTab, setActiveTab] = React.useState("overview"); + + return ( +
+
+
+

Pod: {podName}

+ {namespace} +
+ +
+ + + + Overview + Logs + YAML + Events + + +
+ +
+ + + Pod Information + + +
+ Name + {podName} +
+
+ Namespace + {namespace} +
+
+ Status + Running +
+
+ IP + 10.0.0.1 +
+
+ Node + node-1 +
+
+ Restart Count + 0 +
+
+ Created + 2 hours ago +
+
+
+ + + + Containers + + + + + + Name + Image + State + Ready + + + + + example + nginx:latest + Running + True + + +
+
+
+ + + + Labels + + +
+ app=web + tier=frontend + version=v1 +
+
+
+
+
+ + + + + Container Logs +
+ + +
+
+ +
[INFO] Starting nginx server...
+
[INFO] Listening on port 80
+
[ACCESS] GET / - 200 OK
+
[ACCESS] GET /css/style.css - 200 OK
+
[ACCESS] GET /js/app.js - 200 OK
+
[WARN] Slow response time detected
+
[ACCESS] POST /api/data - 201 Created
+
+
+
+ + + {}} /> + + + + + + + Time + Reason + Type + Message + + + + + 2 hours ago + Pulled + Normal + Container image "nginx:latest" already present on machine + + + 2 hours ago + Created + Normal + Created container example + + + 2 hours ago + Started + Normal + Started container example + + +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/RbacEditor.tsx b/src/components/Kubernetes/RbacEditor.tsx new file mode 100644 index 00000000..5a9e71d5 --- /dev/null +++ b/src/components/Kubernetes/RbacEditor.tsx @@ -0,0 +1,116 @@ +import React from "react"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { Plus, X, Check } from "lucide-react"; +import { Input } from "@/components/ui"; + +interface RbacEditorProps { + _clusterId: string; + namespace: string; + onClose: () => void; +} + +export function RbacEditor({ _clusterId, namespace, onClose }: RbacEditorProps) { + const [activeTab, setActiveTab] = React.useState("roles"); + const [newRoleName, setNewRoleName] = React.useState(""); + + return ( +
+
+

RBAC Editor

+
+ + +
+
+ + + + Roles + ClusterRoles + RoleBindings + ClusterRoleBindings + + +
+ +
+ setNewRoleName(e.target.value)} + /> + +
+ +
+
+
+

Role YAML Editor

+
+
+
+
+ apiVersion: rbac.authorization.k8s.io/v1 +
+
+ kind: Role +
+
+ metadata: +
+
+ name: {newRoleName || "role-name"} +
+
+ namespace: {namespace} +
+
+ rules: +
+
+ - apiGroups: [""] +
+
+ resources: ["pods"] +
+
+ verbs: ["get", "list", "watch"] +
+
+
+
+
+
+ + +
+

ClusterRole editing would be displayed here

+
+
+ + +
+

RoleBinding editing would be displayed here

+
+
+ + +
+

ClusterRoleBinding editing would be displayed here

+
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/RbacViewer.tsx b/src/components/Kubernetes/RbacViewer.tsx new file mode 100644 index 00000000..996e101c --- /dev/null +++ b/src/components/Kubernetes/RbacViewer.tsx @@ -0,0 +1,196 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { Plus, Shield, User } from "lucide-react"; + +interface RbacViewerProps { + clusterId: string; + namespace: string; +} + +export function RbacViewer({ clusterId, namespace }: RbacViewerProps) { + return ( +
+
+
+

RBAC Management

+

Cluster ID: {clusterId} | Namespace: {namespace}

+
+ +
+ +
+
+
+

+ + Roles +

+
+
+ + + + Name + Namespace + Rules + Actions + + + + + pod-reader + {namespace} + get, list, watch pods + + + + + + secret-viewer + {namespace} + get, list secrets + + + + + + deployment-manager + {namespace} + get, list, create, update deployments + + + + + +
+
+
+ +
+
+

+ + ClusterRoles +

+
+
+ + + + Name + Rules + Actions + + + + + admin + Full access to all resources + + + + + + edit + Modify resources in namespace + + + + + + view + Read-only access to resources + + + + + +
+
+
+ +
+
+

+ + RoleBindings +

+
+
+ + + + Name + Role + Subjects + Actions + + + + + pod-reader-binding + pod-reader + user:alice + + + + + + deployment-manager-binding + deployment-manager + group:devs + + + + + +
+
+
+ +
+
+

+ + ClusterRoleBindings +

+
+
+ + + + Name + ClusterRole + Subjects + Actions + + + + + admin-binding + admin + group:admins + + + + + + view-binding + view + group:auditors + + + + + +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/ReplicaSetList.tsx b/src/components/Kubernetes/ReplicaSetList.tsx new file mode 100644 index 00000000..381fcd32 --- /dev/null +++ b/src/components/Kubernetes/ReplicaSetList.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { ReplicaSetInfo } from "@/lib/tauriCommands"; + +interface ReplicaSetListProps { + replicaSets: ReplicaSetInfo[]; + _clusterId: string; + _namespace: string; +} + +export function ReplicaSetList({ replicaSets, _clusterId, _namespace }: ReplicaSetListProps) { + return ( +
+ + + + Name + Namespace + Replicas + Ready + Age + Labels + + + + {replicaSets.length === 0 ? ( + + + No replica sets found + + + ) : ( + replicaSets.map((replicaSet) => ( + + {replicaSet.name} + {replicaSet.namespace} + {replicaSet.replicas} + {replicaSet.ready} + {replicaSet.age} + + {Object.entries(replicaSet.labels) + .map(([k, v]) => `${k}=${v}`) + .join(", ")} + + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/RoleBindingList.tsx b/src/components/Kubernetes/RoleBindingList.tsx new file mode 100644 index 00000000..6f02e186 --- /dev/null +++ b/src/components/Kubernetes/RoleBindingList.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { RoleBindingInfo } from "@/lib/tauriCommands"; + +interface RoleBindingListProps { + roleBindings: RoleBindingInfo[]; + _clusterId: string; + _namespace: string; +} + +export function RoleBindingList({ roleBindings, _clusterId, _namespace }: RoleBindingListProps) { + return ( +
+ + + + Name + Namespace + Role + Age + + + + {roleBindings.length === 0 ? ( + + + No role bindings found + + + ) : ( + roleBindings.map((rb) => ( + + {rb.name} + {rb.namespace} + {rb.role} + {rb.age} + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/RoleList.tsx b/src/components/Kubernetes/RoleList.tsx new file mode 100644 index 00000000..4300cbc0 --- /dev/null +++ b/src/components/Kubernetes/RoleList.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { RoleInfo } from "@/lib/tauriCommands"; + +interface RoleListProps { + roles: RoleInfo[]; + _clusterId: string; + _namespace: string; +} + +export function RoleList({ roles, _clusterId, _namespace }: RoleListProps) { + return ( +
+ + + + Name + Namespace + Age + + + + {roles.length === 0 ? ( + + + No roles found + + + ) : ( + roles.map((role) => ( + + {role.name} + {role.namespace} + {role.age} + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/SearchBar.tsx b/src/components/Kubernetes/SearchBar.tsx new file mode 100644 index 00000000..13076779 --- /dev/null +++ b/src/components/Kubernetes/SearchBar.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { Search } from "lucide-react"; +import { Input } from "@/components/ui"; +import { Button } from "@/components/ui"; + +interface SearchBarProps { + query: string; + onQueryChange: (query: string) => void; + placeholder?: string; + showClear?: boolean; + onClear?: () => void; +} + +export function SearchBar({ query, onQueryChange, placeholder = "Search...", showClear = true, onClear }: SearchBarProps) { + const [isFocused, setIsFocused] = React.useState(false); + + const handleClear = () => { + onQueryChange(""); + onClear?.(); + }; + + return ( +
+ + onQueryChange(e.target.value)} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + placeholder={placeholder} + className="border-none shadow-none focus-visible:ring-0 py-0 px-2 flex-1" + /> + {showClear && query && ( + + )} +
+ ); +} diff --git a/src/components/Kubernetes/SecretDetail.tsx b/src/components/Kubernetes/SecretDetail.tsx new file mode 100644 index 00000000..0c91a43a --- /dev/null +++ b/src/components/Kubernetes/SecretDetail.tsx @@ -0,0 +1,122 @@ +import React from "react"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui"; +import { Badge } from "@/components/ui"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { X } from "lucide-react"; +import { YamlEditor } from "./YamlEditor"; + +interface SecretDetailProps { + secretName: string; + namespace: string; + _clusterId: string; + onClose: () => void; +} + +export function SecretDetail({ secretName, namespace, _clusterId, onClose }: SecretDetailProps) { + const [activeTab, setActiveTab] = React.useState("data"); + const [showValues, setShowValues] = React.useState(false); + + return ( +
+
+
+

Secret: {secretName}

+ Secret +
+ +
+ + + + Data + YAML + Metadata + + +
+ + + +
+ Secret Data + +
+
+ +
+
+ username: + + {showValues ? "admin" : "****"} + +
+
+ password: + + {showValues ? "secret123" : "****"} + +
+
+ api-key: + + {showValues ? "sk-abc123xyz" : "****"} + +
+
+
+
+
+ + + {}} /> + + + +
+ + + Metadata + + +
+ Name + {secretName} +
+
+ Namespace + {namespace} +
+
+ Type + Opaque +
+
+ Created + 2 hours ago +
+
+
+ + + + Labels + + +
+ app=web + tier=frontend +
+
+
+
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/SecretList.tsx b/src/components/Kubernetes/SecretList.tsx new file mode 100644 index 00000000..149c1e40 --- /dev/null +++ b/src/components/Kubernetes/SecretList.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { SecretInfo } from "@/lib/tauriCommands"; + +interface SecretListProps { + secrets: SecretInfo[]; + _clusterId: string; + _namespace: string; +} + +export function SecretList({ secrets, _clusterId, _namespace }: SecretListProps) { + return ( +
+ + + + Name + Namespace + Type + Data Keys + Age + Actions + + + + {secrets.length === 0 ? ( + + + No secrets found + + + ) : ( + secrets.map((secret) => ( + + {secret.name} + {secret.namespace} + {secret.type} + {secret.data_keys} + {secret.age} + + View/Edit + + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/ServiceAccountList.tsx b/src/components/Kubernetes/ServiceAccountList.tsx new file mode 100644 index 00000000..9c25a90b --- /dev/null +++ b/src/components/Kubernetes/ServiceAccountList.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import type { ServiceAccountInfo } from "@/lib/tauriCommands"; + +interface ServiceAccountListProps { + serviceAccounts: ServiceAccountInfo[]; + _clusterId: string; + _namespace: string; +} + +export function ServiceAccountList({ serviceAccounts, _clusterId, _namespace }: ServiceAccountListProps) { + return ( +
+ + + + Name + Namespace + Secrets + Age + + + + {serviceAccounts.length === 0 ? ( + + + No service accounts found + + + ) : ( + serviceAccounts.map((sa) => ( + + {sa.name} + {sa.namespace} + {sa.secrets} + {sa.age} + + )) + )} + +
+
+ ); +} diff --git a/src/components/Kubernetes/ServiceDetail.tsx b/src/components/Kubernetes/ServiceDetail.tsx new file mode 100644 index 00000000..613c5548 --- /dev/null +++ b/src/components/Kubernetes/ServiceDetail.tsx @@ -0,0 +1,157 @@ +import React from "react"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui"; +import { Badge } from "@/components/ui"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui"; +import { Button } from "@/components/ui"; +import { X } from "lucide-react"; +import { YamlEditor } from "./YamlEditor"; + +interface ServiceDetailProps { + serviceName: string; + namespace: string; + _clusterId: string; + onClose: () => void; +} + +export function ServiceDetail({ serviceName, namespace, _clusterId, onClose }: ServiceDetailProps) { + const [activeTab, setActiveTab] = React.useState("overview"); + + return ( +
+
+
+

Service: {serviceName}

+ {namespace} +
+ +
+ + + + Overview + Endpoints + YAML + Events + + +
+ +
+ + + Service Information + + +
+ Name + {serviceName} +
+
+ Namespace + {namespace} +
+
+ Type + ClusterIP +
+
+ Cluster IP + 10.96.0.1 +
+
+ External IP + none +
+
+ Port + 80/TCP +
+
+
+ + + + Selector + + +
+ app=web +
+
+
+ + + + Labels + + +
+ app=web + tier=frontend +
+
+
+
+
+ + + + + + IP + Port + Node + + + + + 10.0.0.1 + 80 + node-1 + + + 10.0.0.2 + 80 + node-2 + + + 10.0.0.3 + 80 + node-3 + + +
+
+ + + {}} /> + + + + + + + Time + Reason + Type + Message + + + + + 2 hours ago + SettingClusterIP + Normal + Assigned cluster IP 10.96.0.1 + + +
+
+
+
+
+ ); +} diff --git a/src/components/Kubernetes/Terminal.tsx b/src/components/Kubernetes/Terminal.tsx new file mode 100644 index 00000000..df8a31e7 --- /dev/null +++ b/src/components/Kubernetes/Terminal.tsx @@ -0,0 +1,150 @@ +import React from "react"; +import { Terminal as TerminalIcon, X, Plus } from "lucide-react"; +import { Button } from "@/components/ui"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui"; + +interface TerminalSession { + id: string; + clusterId: string; + namespace: string; + pod: string; + container: string; + command: string; +} + +interface TerminalProps { + clusterId: string; + namespace: string; +} + +export function Terminal({ clusterId, namespace }: TerminalProps) { + const [sessions, setSessions] = React.useState([]); + const [activeSessionId, setActiveSessionId] = React.useState(null); + const [isCreating, setIsCreating] = React.useState(false); + + const terminalRefs = React.useRef void }>>({}); + const containerRefs = React.useRef>({}); + + const addSession = React.useCallback(() => { + setIsCreating(true); + const newSession: TerminalSession = { + id: `session-${Date.now()}`, + clusterId, + namespace: namespace === "all" ? "default" : namespace, + pod: "", + container: "", + command: "bash", + }; + setSessions((prev) => [...prev, newSession]); + setActiveSessionId(newSession.id); + setIsCreating(false); + }, [clusterId, namespace]); + + const removeSession = (sessionId: string) => { + setSessions((prev) => prev.filter((s) => s.id !== sessionId)); + if (activeSessionId === sessionId) { + setActiveSessionId(null); + } + if (terminalRefs.current[sessionId]) { + terminalRefs.current[sessionId].destroy(); + delete terminalRefs.current[sessionId]; + } + }; + + const resizeTerminal = (sessionId: string) => { + const terminal = terminalRefs.current[sessionId]; + const container = containerRefs.current[sessionId]; + if (terminal && container) { + // Placeholder for resize logic + // Requires xterm-addon-fit dependency + } + }; + + React.useEffect(() => { + // Initialize with a default session + if (sessions.length === 0 && !isCreating) { + addSession(); + } + }, [sessions.length, isCreating, addSession]); + + const initTerminal = (sessionId: string, element: HTMLDivElement | null) => { + if (!element || terminalRefs.current[sessionId]) return; + + // Placeholder for terminal initialization + // Requires xterm, xterm-addon-fit, xterm-addon-web-links dependencies + const terminal = { destroy: () => {} }; + terminalRefs.current[sessionId] = terminal; + containerRefs.current[sessionId] = element; + + // Handle resize + window.addEventListener("resize", () => resizeTerminal(sessionId)); + }; + + return ( +
+
+
+ +

Terminal

+
+ +
+ + {sessions.length === 0 ? ( +
+
+ +

No terminals open

+ +
+
+ ) : ( +
+ + + {sessions.map((session) => ( + + + {session.pod || "new"} / {session.container || "bash"} + + + + ))} + + + {sessions.map((session) => ( + +
initTerminal(session.id, el)} + className="w-full h-full bg-slate-900 rounded-md overflow-hidden" + /> + + ))} + +
+ )} +
+ ); +} diff --git a/src/components/Kubernetes/Toast.tsx b/src/components/Kubernetes/Toast.tsx new file mode 100644 index 00000000..f15499cb --- /dev/null +++ b/src/components/Kubernetes/Toast.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import { X, AlertCircle, CheckCircle, Info, AlertTriangle } from "lucide-react"; +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui"; +import { Button } from "@/components/ui"; + +interface ToastProps { + message: string; + type?: "success" | "error" | "info" | "warning"; + duration?: number; + onClose: () => void; +} + +export function Toast({ message, type = "info", duration = 5000, onClose }: ToastProps) { + const [visible, setVisible] = React.useState(true); + + React.useEffect(() => { + if (duration > 0) { + const timer = setTimeout(() => { + setVisible(false); + setTimeout(onClose, 300); + }, duration); + return () => clearTimeout(timer); + } + }, [duration, onClose]); + + const icons = { + success: , + error: , + info: , + warning: , + }; + + const bgColors = { + success: "bg-green-50 border-green-200", + error: "bg-red-50 border-red-200", + info: "bg-blue-50 border-blue-200", + warning: "bg-yellow-50 border-yellow-200", + }; + + return ( +
+ + +
+
+ {icons[type]} + + {type.charAt(0).toUpperCase() + type.slice(1)} + +
+ +
+
+ +

{message}

+
+
+
+ ); +} diff --git a/src/components/Kubernetes/YamlEditor.tsx b/src/components/Kubernetes/YamlEditor.tsx new file mode 100644 index 00000000..4cbd168e --- /dev/null +++ b/src/components/Kubernetes/YamlEditor.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { Button } from "@/components/ui"; +import { Badge } from "@/components/ui"; + +interface YamlEditorProps { + onChange: (value: string) => void; +} + +export function YamlEditor({ onChange }: YamlEditorProps) { + return ( +
+
+
+

YAML Editor

+ Ready +
+
+ + +
+
+ +
+
+

YAML Editor would be displayed here

+

Requires @monaco-editor/react dependency

+
+
+
+ ); +} diff --git a/src/components/Kubernetes/index.tsx b/src/components/Kubernetes/index.tsx index 34159051..3619ee2d 100644 --- a/src/components/Kubernetes/index.tsx +++ b/src/components/Kubernetes/index.tsx @@ -8,3 +8,40 @@ export { ServiceList } from "./ServiceList"; export { DeploymentList } from "./DeploymentList"; export { StatefulSetList } from "./StatefulSetList"; export { DaemonSetList } from "./DaemonSetList"; +export { NodeList } from "./NodeList"; +export { EventList } from "./EventList"; +export { ConfigMapList } from "./ConfigMapList"; +export { SecretList } from "./SecretList"; +export { ReplicaSetList } from "./ReplicaSetList"; +export { JobList } from "./JobList"; +export { CronJobList } from "./CronJobList"; +export { IngressList } from "./IngressList"; +export { PVCList } from "./PVCList"; +export { PVList } from "./PVList"; +export { ServiceAccountList } from "./ServiceAccountList"; +export { RoleList } from "./RoleList"; +export { ClusterRoleList } from "./ClusterRoleList"; +export { RoleBindingList } from "./RoleBindingList"; +export { ClusterRoleBindingList } from "./ClusterRoleBindingList"; +export { HPAList } from "./HPAList"; +export { Terminal } from "./Terminal"; +export { YamlEditor } from "./YamlEditor"; +export { MetricsChart } from "./MetricsChart"; +export { SearchBar } from "./SearchBar"; +export { ContextSwitcher } from "./ContextSwitcher"; +export { ApplicationView } from "./ApplicationView"; +export { PodDetail } from "./PodDetail"; +export { DeploymentDetail } from "./DeploymentDetail"; +export { ServiceDetail } from "./ServiceDetail"; +export { ConfigMapDetail } from "./ConfigMapDetail"; +export { SecretDetail } from "./SecretDetail"; +export { ClusterOverview } from "./ClusterOverview"; +export { ClusterDetails } from "./ClusterDetails"; +export { Hotbar } from "./Hotbar"; +export { CommandPalette } from "./CommandPalette"; +export { Toast } from "./Toast"; +export { LoadingSpinner } from "./LoadingSpinner"; +export { CreateResourceModal } from "./CreateResourceModal"; +export { EditResourceModal } from "./EditResourceModal"; +export { RbacViewer } from "./RbacViewer"; +export { RbacEditor } from "./RbacEditor"; diff --git a/src/lib/eventBus.ts b/src/lib/eventBus.ts new file mode 100644 index 00000000..62fd8a50 --- /dev/null +++ b/src/lib/eventBus.ts @@ -0,0 +1,116 @@ +import { invoke } from "@tauri-apps/api/core"; + +export type EventCallback = (data: T) => void; + +export interface EventUnsubscribe { + (): void; +} + +export interface EventBus { + on(event: string, callback: EventCallback): EventUnsubscribe; + off(event: string, callback: EventCallback): void; + emit(event: string, data?: T): void; + once(event: string, callback: EventCallback): EventUnsubscribe; +} + +class SimpleEventBus implements EventBus { + private events: Record> = {}; + private onceEvents: Record> = {}; + + on(event: string, callback: EventCallback): EventUnsubscribe { + if (!this.events[event]) { + this.events[event] = new Set(); + } + this.events[event].add(callback); + + return () => this.off(event, callback); + } + + off(event: string, callback: EventCallback): void { + if (this.events[event]) { + this.events[event].delete(callback); + } + } + + emit(event: string, data?: T): void { + const callbacks = this.events[event]; + if (callbacks) { + callbacks.forEach((callback) => callback(data as T)); + } + + const onceCallbacks = this.onceEvents[event]; + if (onceCallbacks) { + onceCallbacks.forEach((callback) => callback(data as T)); + delete this.onceEvents[event]; + } + } + + once(event: string, callback: EventCallback): EventUnsubscribe { + if (!this.onceEvents[event]) { + this.onceEvents[event] = new Set(); + } + this.onceEvents[event].add(callback); + + return () => { + if (this.onceEvents[event]) { + this.onceEvents[event].delete(callback); + } + }; + } +} + +export const eventBus: EventBus = new SimpleEventBus(); + +export async function subscribeToK8sEvents( + clusterId: string, + namespace: string, + resourceType: string, + callback: EventCallback +): Promise { + try { + const unsubscribeId = await invoke("subscribe_to_k8s_events", { + clusterId, + namespace, + resourceType, + }); + + const handler = (data: any) => { + callback(data); + }; + + eventBus.on(`k8s:${clusterId}:${namespace}:${resourceType}`, handler); + + return () => { + eventBus.off(`k8s:${clusterId}:${namespace}:${resourceType}`, handler); + invoke("unsubscribe_from_k8s_events", { unsubscribeId }); + }; + } catch (error) { + console.error("Failed to subscribe to K8s events:", error); + return () => {}; + } +} + +export async function subscribeToAllEvents( + clusterId: string, + callback: EventCallback +): Promise { + try { + const unsubscribeId = await invoke("subscribe_to_all_k8s_events", { + clusterId, + }); + + const handler = (data: any) => { + callback(data); + }; + + eventBus.on(`k8s:${clusterId}:all`, handler); + + return () => { + eventBus.off(`k8s:${clusterId}:all`, handler); + invoke("unsubscribe_from_k8s_events", { unsubscribeId }); + }; + } catch (error) { + console.error("Failed to subscribe to all K8s events:", error); + return () => {}; + } +} diff --git a/src/lib/tauriCommands.ts b/src/lib/tauriCommands.ts index 1bbb53c4..054c9523 100644 --- a/src/lib/tauriCommands.ts +++ b/src/lib/tauriCommands.ts @@ -748,6 +748,18 @@ export interface ClusterInfo { cluster_url: string; } +export interface ContextInfo { + name: string; + cluster: string; + user: string; +} + +export interface ResourceInfo { + name: string; + namespace: string; + [key: string]: unknown; +} + export interface PortForwardRequest { cluster_id: string; namespace: string; diff --git a/src/pages/Kubernetes/KubernetesPage.tsx b/src/pages/Kubernetes/KubernetesPage.tsx index 7bb2b5b6..7cfcac60 100644 --- a/src/pages/Kubernetes/KubernetesPage.tsx +++ b/src/pages/Kubernetes/KubernetesPage.tsx @@ -1,8 +1,10 @@ import React, { useState, useEffect } from "react"; +import { useKubernetesStore } from "@/stores/kubernetesStore"; import { ClusterList } from "@/components/Kubernetes/ClusterList"; import { PortForwardList } from "@/components/Kubernetes/PortForwardList"; import { AddClusterModal } from "@/components/Kubernetes/AddClusterModal"; import { PortForwardForm } from "@/components/Kubernetes/PortForwardForm"; +import { ResourceBrowser } from "@/components/Kubernetes/ResourceBrowser"; import type { ClusterInfo, PortForwardResponse } from "@/lib/tauriCommands"; import { listClustersCmd, @@ -13,7 +15,7 @@ import { } from "@/lib/tauriCommands"; export function KubernetesPage() { - const [clusters, setClusters] = useState([]); + const { clusters, addCluster, removeCluster, selectedClusterId } = useKubernetesStore(); const [portForwards, setPortForwards] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isAddClusterOpen, setIsAddClusterOpen] = useState(false); @@ -30,7 +32,8 @@ export function KubernetesPage() { listClustersCmd(), listPortForwardsCmd(), ]); - setClusters(clustersData); + + clustersData.forEach(addCluster); setPortForwards(portForwardsData); } catch (err) { console.error("Failed to load data:", err); @@ -42,7 +45,7 @@ export function KubernetesPage() { const handleRemoveCluster = async (clusterId: string) => { try { await removeClusterCmd(clusterId); - setClusters((prev) => prev.filter((c) => c.id !== clusterId)); + removeCluster(clusterId); } catch (err) { console.error("Failed to remove cluster:", err); alert("Failed to remove cluster"); @@ -70,7 +73,7 @@ export function KubernetesPage() { }; const handleAddCluster = (cluster: ClusterInfo) => { - setClusters((prev) => [...prev, cluster]); + addCluster(cluster); }; const handleStartPortForward = (portForward: PortForwardResponse) => { @@ -93,17 +96,41 @@ export function KubernetesPage() {

Kubernetes Management

- Manage your Kubernetes clusters and port forwarding sessions + Manage your Kubernetes clusters and resources

-
+ {/* Cluster Management Section */} +
+
+

Clusters

+ +
+ setIsAddClusterOpen(true)} onRemove={handleRemoveCluster} /> +
+ {/* Port Forwarding Section */} +
+
+

Port Forwarding

+ +
+ setIsStartPortForwardOpen(true)} @@ -112,12 +139,22 @@ export function KubernetesPage() { />
+ {/* Resource Browser Section */} + {selectedClusterId && ( +
+

Resource Browser

+ +
+ )} + + {/* Add Cluster Modal */} setIsAddClusterOpen(false)} onAdd={handleAddCluster} /> + {/* Port Forward Form */} setIsStartPortForwardOpen(false)} diff --git a/src/stores/kubernetesStore.ts b/src/stores/kubernetesStore.ts new file mode 100644 index 00000000..2cca272a --- /dev/null +++ b/src/stores/kubernetesStore.ts @@ -0,0 +1,185 @@ +import { create } from "zustand"; +import type { ClusterInfo, ContextInfo, ResourceInfo } from "@/lib/tauriCommands"; + +export type ResourceType = + | "pods" + | "services" + | "deployments" + | "statefulsets" + | "daemonsets" + | "replicasets" + | "jobs" + | "cronjobs" + | "ingresses" + | "persistentvolumes" + | "persistentvolumeclaims" + | "configmaps" + | "secrets" + | "serviceaccounts" + | "roles" + | "clusterroles" + | "rolebindings" + | "clusterrolebindings" + | "nodes" + | "events" + | "hpas"; + +interface KubernetesState { + // Selection state + selectedClusterId: string | null; + selectedNamespace: string; + + // Data state + clusters: ClusterInfo[]; + contexts: ContextInfo[]; + namespaces: Record; // clusterId -> [namespaces] + + // Loaded resources tracking + loadedResources: Set; + + // Terminal sessions + terminalSessions: Record; + nextTerminalId: number; + + // Search state + globalSearchQuery: string; + searchResults: Record; + + // Bulk selection + bulkSelection: Record; // resourceType -> [resourceNames] + + // Actions + setSelectedCluster: (clusterId: string) => void; + setSelectedNamespace: (namespace: string) => void; + addCluster: (cluster: ClusterInfo) => void; + removeCluster: (clusterId: string) => void; + updateCluster: (clusterId: string, updates: Partial) => void; + addContext: (context: ContextInfo) => void; + setNamespaces: (clusterId: string, namespaces: string[]) => void; + markResourceLoaded: (type: ResourceType) => void; + markResourceUnloaded: (type: ResourceType) => void; + isResourceLoaded: (type: ResourceType) => boolean; + addTerminalSession: (session: { clusterId: string; namespace: string; pod: string; container: string; command: string }) => string; + removeTerminalSession: (sessionId: string) => void; + setGlobalSearchQuery: (query: string) => void; + setSearchResults: (type: ResourceType, results: ResourceInfo[]) => void; + addToBulkSelection: (type: ResourceType, resourceName: string) => void; + removeFromBulkSelection: (type: ResourceType, resourceName: string) => void; + clearBulkSelection: (type: ResourceType) => void; + getBulkSelectionCount: (type: ResourceType) => number; +} + +export const useKubernetesStore = create()((set, get) => ({ + // Selection state + selectedClusterId: null, + selectedNamespace: "all", + + // Data state + clusters: [], + contexts: [], + namespaces: {}, + + // Loaded resources tracking + loadedResources: new Set(), + + // Terminal sessions + terminalSessions: {}, + nextTerminalId: 1, + + // Search state + globalSearchQuery: "", + searchResults: {} as Record, + + // Bulk selection + bulkSelection: {} as Record, + + // Actions + setSelectedCluster: (clusterId) => set({ selectedClusterId: clusterId, selectedNamespace: "all" }), + + setSelectedNamespace: (namespace) => set({ selectedNamespace: namespace }), + + addCluster: (cluster) => set((state) => ({ + clusters: [...state.clusters, cluster], + })), + + removeCluster: (clusterId) => set((state) => ({ + clusters: state.clusters.filter((c) => c.id !== clusterId), + selectedClusterId: state.selectedClusterId === clusterId ? null : state.selectedClusterId, + })), + + updateCluster: (clusterId, updates) => set((state) => ({ + clusters: state.clusters.map((c) => + c.id === clusterId ? { ...c, ...updates } : c + ), + })), + + addContext: (context) => set((state) => ({ + contexts: [...state.contexts, context], + })), + + setNamespaces: (clusterId, namespaces) => set((state) => ({ + namespaces: { ...state.namespaces, [clusterId]: namespaces }, + })), + + markResourceLoaded: (type) => set((state) => { + const newSet = new Set(state.loadedResources); + newSet.add(type); + return { loadedResources: newSet }; + }), + + markResourceUnloaded: (type) => set((state) => { + const newSet = new Set(state.loadedResources); + newSet.delete(type); + return { loadedResources: newSet }; + }), + + isResourceLoaded: (type) => get().loadedResources.has(type), + + addTerminalSession: (session) => { + const sessionId = `terminal-${get().nextTerminalId}`; + set((state) => ({ + terminalSessions: { ...state.terminalSessions, [sessionId]: { id: sessionId, ...session } }, + nextTerminalId: state.nextTerminalId + 1, + })); + return sessionId; + }, + + removeTerminalSession: (sessionId) => set((state) => ({ + terminalSessions: Object.fromEntries( + Object.entries(state.terminalSessions).filter(([id]) => id !== sessionId) + ), + })), + + setGlobalSearchQuery: (query) => set({ globalSearchQuery: query }), + + setSearchResults: (type, results) => set((state) => ({ + searchResults: { ...state.searchResults, [type]: results }, + })), + + addToBulkSelection: (type, resourceName) => set((state) => ({ + bulkSelection: { + ...state.bulkSelection, + [type]: [...(state.bulkSelection[type] || []), resourceName], + }, + })), + + removeFromBulkSelection: (type, resourceName) => set((state) => ({ + bulkSelection: { + ...state.bulkSelection, + [type]: (state.bulkSelection[type] || []).filter((name) => name !== resourceName), + }, + })), + + clearBulkSelection: (type) => set((state) => ({ + bulkSelection: { ...state.bulkSelection, [type]: [] }, + })), + + getBulkSelectionCount: (type) => (get().bulkSelection[type] || []).length, +})); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 00000000..195f1aad --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +declare module '*.css' { + const classes: { [key: string]: string }; + export default classes; +} diff --git a/tests/unit/kubernetesStore.test.ts b/tests/unit/kubernetesStore.test.ts new file mode 100644 index 00000000..1a125206 --- /dev/null +++ b/tests/unit/kubernetesStore.test.ts @@ -0,0 +1,161 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { useKubernetesStore } from "@/stores/kubernetesStore"; +import type { ResourceInfo } from "@/lib/tauriCommands"; + +describe("Kubernetes Store", () => { + beforeEach(() => { + useKubernetesStore.getState().clusters.forEach((c) => + useKubernetesStore.getState().removeCluster(c.id) + ); + }); + + describe("Cluster Management", () => { + it("should add a cluster", () => { + const cluster = { + id: "cluster-1", + name: "Production", + context: "prod-context", + cluster_url: "https://k8s.example.com", + }; + + useKubernetesStore.getState().addCluster(cluster); + + expect(useKubernetesStore.getState().clusters).toHaveLength(1); + expect(useKubernetesStore.getState().clusters[0].name).toBe("Production"); + }); + + it("should remove a cluster", () => { + const cluster = { + id: "cluster-1", + name: "Production", + context: "prod-context", + cluster_url: "https://k8s.example.com", + }; + + useKubernetesStore.getState().addCluster(cluster); + useKubernetesStore.getState().removeCluster("cluster-1"); + + expect(useKubernetesStore.getState().clusters).toHaveLength(0); + }); + + it("should update a cluster", () => { + const cluster = { + id: "cluster-1", + name: "Production", + context: "prod-context", + cluster_url: "https://k8s.example.com", + }; + + useKubernetesStore.getState().addCluster(cluster); + useKubernetesStore.getState().updateCluster("cluster-1", { name: "Production New" }); + + expect(useKubernetesStore.getState().clusters[0].name).toBe("Production New"); + }); + + it("should set selected cluster", () => { + const cluster = { + id: "cluster-1", + name: "Production", + context: "prod-context", + cluster_url: "https://k8s.example.com", + }; + + useKubernetesStore.getState().addCluster(cluster); + useKubernetesStore.getState().setSelectedCluster("cluster-1"); + + expect(useKubernetesStore.getState().selectedClusterId).toBe("cluster-1"); + }); + }); + + describe("Namespace Management", () => { + it("should set selected namespace", () => { + useKubernetesStore.getState().setSelectedNamespace("default"); + expect(useKubernetesStore.getState().selectedNamespace).toBe("default"); + }); + + it("should set namespaces for a cluster", () => { + useKubernetesStore.getState().setNamespaces("cluster-1", ["default", "kube-system", "production"]); + expect(useKubernetesStore.getState().namespaces["cluster-1"]).toEqual(["default", "kube-system", "production"]); + }); + }); + + describe("Resource Loading", () => { + it("should mark resource as loaded", () => { + useKubernetesStore.getState().markResourceLoaded("pods"); + expect(useKubernetesStore.getState().isResourceLoaded("pods")).toBe(true); + }); + + it("should mark resource as unloaded", () => { + useKubernetesStore.getState().markResourceLoaded("pods"); + useKubernetesStore.getState().markResourceUnloaded("pods"); + expect(useKubernetesStore.getState().isResourceLoaded("pods")).toBe(false); + }); + }); + + describe("Terminal Sessions", () => { + it("should add a terminal session", () => { + const sessionId = useKubernetesStore.getState().addTerminalSession({ + clusterId: "cluster-1", + namespace: "default", + pod: "nginx", + container: "nginx", + command: "bash", + }); + + expect(sessionId).toBe("terminal-1"); + expect(useKubernetesStore.getState().terminalSessions[sessionId]).toBeDefined(); + }); + + it("should remove a terminal session", () => { + const sessionId = useKubernetesStore.getState().addTerminalSession({ + clusterId: "cluster-1", + namespace: "default", + pod: "nginx", + container: "nginx", + command: "bash", + }); + + useKubernetesStore.getState().removeTerminalSession(sessionId); + expect(useKubernetesStore.getState().terminalSessions[sessionId]).toBeUndefined(); + }); + }); + + describe("Search", () => { + it("should set global search query", () => { + useKubernetesStore.getState().setGlobalSearchQuery("nginx"); + expect(useKubernetesStore.getState().globalSearchQuery).toBe("nginx"); + }); + + it("should set search results", () => { + const results = [{ name: "nginx-1", namespace: "default" }]; + useKubernetesStore.getState().setSearchResults("pods", results as ResourceInfo[]); + + expect(useKubernetesStore.getState().searchResults.pods).toEqual(results); + }); + }); + + describe("Bulk Selection", () => { + it("should add to bulk selection", () => { + useKubernetesStore.getState().addToBulkSelection("pods", "nginx-1"); + expect(useKubernetesStore.getState().bulkSelection.pods).toContain("nginx-1"); + }); + + it("should remove from bulk selection", () => { + useKubernetesStore.getState().addToBulkSelection("pods", "nginx-1"); + useKubernetesStore.getState().removeFromBulkSelection("pods", "nginx-1"); + expect(useKubernetesStore.getState().bulkSelection.pods).not.toContain("nginx-1"); + }); + + it("should clear bulk selection", () => { + useKubernetesStore.getState().addToBulkSelection("pods", "nginx-1"); + useKubernetesStore.getState().clearBulkSelection("pods"); + expect(useKubernetesStore.getState().bulkSelection.pods).toEqual([]); + }); + + it("should get bulk selection count", () => { + useKubernetesStore.getState().addToBulkSelection("pods", "nginx-1"); + useKubernetesStore.getState().addToBulkSelection("pods", "nginx-2"); + expect(useKubernetesStore.getState().getBulkSelectionCount("pods")).toBe(2); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 504e61b7..704a6a36 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,8 +15,9 @@ "noUnusedLocals": false, "noUnusedParameters": false, "noFallthroughCasesInSwitch": true, + "ignoreDeprecations": "6.0", "baseUrl": ".", - "paths": { "@/*": ["src/*"] }, + "paths": { "@/*": ["./src/*"] }, "types": ["vitest/globals", "@testing-library/jest-dom", "node"] }, "include": ["src", "tests/unit"],