@skyhook-io/k8s-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +63 -0
- package/src/components/dock/BottomDock.tsx +204 -0
- package/src/components/dock/DockContext.tsx +212 -0
- package/src/components/dock/TerminalTab.tsx +333 -0
- package/src/components/dock/index.ts +3 -0
- package/src/components/gitops/GitOpsActions.tsx +227 -0
- package/src/components/gitops/GitOpsStatusBadge.tsx +221 -0
- package/src/components/gitops/ManagedResourcesList.tsx +210 -0
- package/src/components/gitops/SyncCountdown.tsx +146 -0
- package/src/components/gitops/index.ts +4 -0
- package/src/components/logs/JsonLogLine.tsx +90 -0
- package/src/components/logs/LogCore.tsx +553 -0
- package/src/components/logs/LogToolbarSelects.tsx +74 -0
- package/src/components/logs/LogsViewer.tsx +146 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +263 -0
- package/src/components/logs/index.ts +13 -0
- package/src/components/logs/useLogBuffer.ts +138 -0
- package/src/components/logs/useLogSearch.ts +151 -0
- package/src/components/logs/useLogStream.ts +61 -0
- package/src/components/resources/ResourcesView.tsx +5494 -0
- package/src/components/resources/index.ts +17 -0
- package/src/components/resources/renderers/AlertRenderer.tsx +166 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +233 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +89 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +194 -0
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +79 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +68 -0
- package/src/components/resources/renderers/CertificateRenderer.tsx +209 -0
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +123 -0
- package/src/components/resources/renderers/ChallengeRenderer.tsx +111 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +164 -0
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +140 -0
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +153 -0
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +221 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +124 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +86 -0
- package/src/components/resources/renderers/EventRenderer.tsx +175 -0
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +119 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +172 -0
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +242 -0
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +263 -0
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +68 -0
- package/src/components/resources/renderers/GatewayRenderer.tsx +218 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +476 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +154 -0
- package/src/components/resources/renderers/HPARenderer.tsx +164 -0
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +303 -0
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +167 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +49 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +104 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +175 -0
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +176 -0
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +125 -0
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +84 -0
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +112 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +288 -0
- package/src/components/resources/renderers/JobRenderer.tsx +103 -0
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +157 -0
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +188 -0
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +148 -0
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +90 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +136 -0
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +151 -0
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +70 -0
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +309 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +142 -0
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +232 -0
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +97 -0
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +74 -0
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +138 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +220 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +151 -0
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +352 -0
- package/src/components/resources/renderers/LeaseRenderer.tsx +93 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +208 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +282 -0
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +153 -0
- package/src/components/resources/renderers/OrderRenderer.tsx +108 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +98 -0
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +138 -0
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +126 -0
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +63 -0
- package/src/components/resources/renderers/PodRenderer.tsx +677 -0
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +50 -0
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +62 -0
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +71 -0
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +97 -0
- package/src/components/resources/renderers/RoleRenderer.tsx +192 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +227 -0
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +75 -0
- package/src/components/resources/renderers/SbomReportRenderer.tsx +98 -0
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +121 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +321 -0
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +111 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +81 -0
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +65 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +98 -0
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +195 -0
- package/src/components/resources/renderers/StorageClassRenderer.tsx +82 -0
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +257 -0
- package/src/components/resources/renderers/VPARenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +73 -0
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +161 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +134 -0
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +36 -0
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +347 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +156 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +202 -0
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +91 -0
- package/src/components/resources/renderers/WorkloadRenderer.tsx +311 -0
- package/src/components/resources/renderers/argo-cells.tsx +118 -0
- package/src/components/resources/renderers/certmanager-cells.tsx +164 -0
- package/src/components/resources/renderers/cnpg-cells.tsx +161 -0
- package/src/components/resources/renderers/eso-cells.tsx +115 -0
- package/src/components/resources/renderers/flux-cells.tsx +253 -0
- package/src/components/resources/renderers/index.ts +105 -0
- package/src/components/resources/renderers/istio-cells.tsx +210 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +107 -0
- package/src/components/resources/renderers/keda-cells.tsx +94 -0
- package/src/components/resources/renderers/knative-cells.tsx +396 -0
- package/src/components/resources/renderers/kyverno-cells.tsx +85 -0
- package/src/components/resources/renderers/prometheus-cells.tsx +74 -0
- package/src/components/resources/renderers/traefik-cells.tsx +120 -0
- package/src/components/resources/renderers/trivy-cells.tsx +153 -0
- package/src/components/resources/renderers/trivy-shared.tsx +67 -0
- package/src/components/resources/renderers/velero-cells.tsx +164 -0
- package/src/components/resources/resource-utils-argo.ts +155 -0
- package/src/components/resources/resource-utils-certmanager.ts +198 -0
- package/src/components/resources/resource-utils-cnpg.ts +344 -0
- package/src/components/resources/resource-utils-eso.ts +385 -0
- package/src/components/resources/resource-utils-flux.ts +186 -0
- package/src/components/resources/resource-utils-istio.ts +372 -0
- package/src/components/resources/resource-utils-karpenter.ts +150 -0
- package/src/components/resources/resource-utils-keda.ts +203 -0
- package/src/components/resources/resource-utils-knative.ts +218 -0
- package/src/components/resources/resource-utils-kyverno.ts +224 -0
- package/src/components/resources/resource-utils-prometheus.ts +159 -0
- package/src/components/resources/resource-utils-traefik.ts +180 -0
- package/src/components/resources/resource-utils-trivy.ts +146 -0
- package/src/components/resources/resource-utils-velero.ts +293 -0
- package/src/components/resources/resource-utils.ts +1558 -0
- package/src/components/shared/EditableYamlView.tsx +302 -0
- package/src/components/shared/ResourceActionsBar.tsx +924 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +557 -0
- package/src/components/shared/index.ts +3 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/shared.tsx +614 -0
- package/src/components/topology/GroupNode.tsx +198 -0
- package/src/components/topology/K8sResourceNode.tsx +440 -0
- package/src/components/topology/TopologyControls.tsx +62 -0
- package/src/components/topology/TopologyFilterSidebar.tsx +377 -0
- package/src/components/topology/TopologyGraph.tsx +748 -0
- package/src/components/topology/TopologySearch.tsx +304 -0
- package/src/components/topology/index.ts +7 -0
- package/src/components/topology/layout.ts +734 -0
- package/src/components/topology/layout.worker.ts +272 -0
- package/src/components/topology/topology.css +129 -0
- package/src/components/ui/CodeViewer.tsx +451 -0
- package/src/components/ui/ConfirmDialog.tsx +188 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +57 -0
- package/src/components/ui/HealthRing.tsx +72 -0
- package/src/components/ui/MetricsChart.tsx +259 -0
- package/src/components/ui/ResourceBar.tsx +68 -0
- package/src/components/ui/Toast.tsx +225 -0
- package/src/components/ui/Tooltip.tsx +165 -0
- package/src/components/ui/YamlEditor.tsx +280 -0
- package/src/components/ui/drawer-components.tsx +792 -0
- package/src/components/ui/index.ts +10 -0
- package/src/components/workload/ResourceDetailDrawer.tsx +158 -0
- package/src/components/workload/WorkloadView.tsx +1109 -0
- package/src/components/workload/index.ts +2 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useAnimatedUnmount.ts +30 -0
- package/src/hooks/useKeyboardShortcuts.tsx +336 -0
- package/src/hooks/useRefreshAnimation.ts +58 -0
- package/src/index.ts +35 -0
- package/src/types/core.ts +894 -0
- package/src/types/gitops.ts +458 -0
- package/src/types/index.ts +2 -0
- package/src/utils/animation.ts +40 -0
- package/src/utils/api-resources.ts +185 -0
- package/src/utils/badge-colors.ts +399 -0
- package/src/utils/context-name.ts +72 -0
- package/src/utils/download.ts +19 -0
- package/src/utils/format.ts +233 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/log-format.ts +300 -0
- package/src/utils/navigation.ts +119 -0
- package/src/utils/resource-hierarchy.ts +630 -0
- package/src/utils/resource-icons.ts +206 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +24 -0
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skyhook-io/k8s-ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/skyhook-io/radar",
|
|
7
|
+
"directory": "packages/k8s-ui"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./src/index.ts",
|
|
11
|
+
"types": "./src/index.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./src/index.ts",
|
|
14
|
+
"./types": "./src/types/index.ts",
|
|
15
|
+
"./utils": "./src/utils/index.ts",
|
|
16
|
+
"./components/resources/*": "./src/components/resources/*.ts",
|
|
17
|
+
"./components/ui/*": "./src/components/ui/*.tsx",
|
|
18
|
+
"./hooks/*": "./src/hooks/*.ts",
|
|
19
|
+
"./resources": "./src/components/resources/index.ts",
|
|
20
|
+
"./logs": "./src/components/logs/index.ts",
|
|
21
|
+
"./timeline": "./src/components/timeline/index.ts",
|
|
22
|
+
"./topology": "./src/components/topology/index.ts"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"tsc": "tsc --noEmit",
|
|
26
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@monaco-editor/react": "^4.7.0",
|
|
30
|
+
"react-virtuoso": "^4.18.1",
|
|
31
|
+
"shiki": "^4.0.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@xterm/addon-fit": ">=0.10.0",
|
|
35
|
+
"@xterm/addon-web-links": ">=0.11.0",
|
|
36
|
+
"@xterm/xterm": ">=5.0.0",
|
|
37
|
+
"@xyflow/react": ">=12.0.0",
|
|
38
|
+
"clsx": ">=2.0.0",
|
|
39
|
+
"diff": ">=5.0.0",
|
|
40
|
+
"elkjs": ">=0.9.0",
|
|
41
|
+
"lucide-react": ">=0.400.0",
|
|
42
|
+
"react": ">=18.0.0",
|
|
43
|
+
"react-dom": ">=18.0.0",
|
|
44
|
+
"yaml": ">=2.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/diff": "^7.0.2",
|
|
48
|
+
"@types/react": "^19.2.14",
|
|
49
|
+
"@types/react-dom": "^19.2.3",
|
|
50
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
51
|
+
"@xterm/addon-web-links": "^0.12.0",
|
|
52
|
+
"@xterm/xterm": "^6.0.0",
|
|
53
|
+
"@xyflow/react": "^12.10.1",
|
|
54
|
+
"clsx": "^2.1.1",
|
|
55
|
+
"diff": "^8.0.3",
|
|
56
|
+
"elkjs": "^0.11.1",
|
|
57
|
+
"lucide-react": "^0.575.0",
|
|
58
|
+
"react": "^19.2.4",
|
|
59
|
+
"react-dom": "^19.2.4",
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"yaml": "^2.7.1"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect, ReactNode } from 'react'
|
|
2
|
+
import { DURATION_DOCK } from '../../utils/animation'
|
|
3
|
+
import { X, ChevronDown, ChevronUp, Terminal, FileText, Trash2, Layers, Maximize2, Minimize2 } from 'lucide-react'
|
|
4
|
+
import { clsx } from 'clsx'
|
|
5
|
+
import { useDock, DockTab } from './DockContext'
|
|
6
|
+
import { useRegisterShortcut } from '../../hooks/useKeyboardShortcuts'
|
|
7
|
+
|
|
8
|
+
const MIN_HEIGHT = 200
|
|
9
|
+
const DEFAULT_HEIGHT = 400
|
|
10
|
+
const MAX_HEIGHT_RATIO = 0.7
|
|
11
|
+
const MAXIMIZED_TOP_OFFSET = 48
|
|
12
|
+
|
|
13
|
+
interface BottomDockProps {
|
|
14
|
+
renderTabContent: (tab: DockTab, isActive: boolean) => ReactNode
|
|
15
|
+
/** Offset from the left edge in px — use to avoid overlapping a fixed sidebar */
|
|
16
|
+
leftOffset?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function BottomDock({ renderTabContent, leftOffset = 0 }: BottomDockProps) {
|
|
20
|
+
const { tabs, activeTabId, isExpanded, removeTab, setActiveTab, toggleExpanded, closeAll } = useDock()
|
|
21
|
+
const [height, setHeight] = useState(DEFAULT_HEIGHT)
|
|
22
|
+
const [isMaximized, setIsMaximized] = useState(false)
|
|
23
|
+
const isDragging = useRef(false)
|
|
24
|
+
const startY = useRef(0)
|
|
25
|
+
const startHeight = useRef(0)
|
|
26
|
+
|
|
27
|
+
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
|
28
|
+
isDragging.current = true
|
|
29
|
+
startY.current = e.clientY
|
|
30
|
+
startHeight.current = height
|
|
31
|
+
document.body.style.cursor = 'ns-resize'
|
|
32
|
+
document.body.style.userSelect = 'none'
|
|
33
|
+
}, [height])
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
37
|
+
if (!isDragging.current) return
|
|
38
|
+
const maxHeight = window.innerHeight * MAX_HEIGHT_RATIO
|
|
39
|
+
const newHeight = Math.min(maxHeight, Math.max(MIN_HEIGHT, startHeight.current - (e.clientY - startY.current)))
|
|
40
|
+
setHeight(newHeight)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handleMouseUp = () => {
|
|
44
|
+
isDragging.current = false
|
|
45
|
+
document.body.style.cursor = ''
|
|
46
|
+
document.body.style.userSelect = ''
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
document.addEventListener('mousemove', handleMouseMove)
|
|
50
|
+
document.addEventListener('mouseup', handleMouseUp)
|
|
51
|
+
|
|
52
|
+
return () => {
|
|
53
|
+
document.removeEventListener('mousemove', handleMouseMove)
|
|
54
|
+
document.removeEventListener('mouseup', handleMouseUp)
|
|
55
|
+
}
|
|
56
|
+
}, [])
|
|
57
|
+
|
|
58
|
+
const toggleMaximized = useCallback(() => {
|
|
59
|
+
setIsMaximized(prev => !prev)
|
|
60
|
+
}, [])
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!isMaximized) return
|
|
64
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
65
|
+
if (e.key === 'Escape') setIsMaximized(false)
|
|
66
|
+
}
|
|
67
|
+
window.addEventListener('keydown', handleKeyDown)
|
|
68
|
+
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
69
|
+
}, [isMaximized])
|
|
70
|
+
|
|
71
|
+
useRegisterShortcut({
|
|
72
|
+
id: 'dock-toggle',
|
|
73
|
+
keys: 'Ctrl+`',
|
|
74
|
+
description: 'Toggle dock',
|
|
75
|
+
category: 'Dock',
|
|
76
|
+
scope: 'global',
|
|
77
|
+
handler: () => toggleExpanded(),
|
|
78
|
+
enabled: tabs.length > 0,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if (tabs.length === 0) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const effectiveHeight = !isExpanded ? 36 : isMaximized ? `calc(100vh - ${MAXIMIZED_TOP_OFFSET}px)` : height
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
className="fixed bottom-0 right-0 bg-theme-base border-t border-theme-border flex flex-col z-40"
|
|
90
|
+
style={{ height: effectiveHeight, left: leftOffset, transition: `height ${DURATION_DOCK}ms ease-out, left ${DURATION_DOCK}ms ease-out` }}
|
|
91
|
+
>
|
|
92
|
+
{isExpanded && !isMaximized && (
|
|
93
|
+
<div
|
|
94
|
+
className="absolute top-0 left-0 right-0 h-1 cursor-ns-resize hover:bg-blue-500/50 transition-colors"
|
|
95
|
+
onMouseDown={handleMouseDown}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
<div className="flex items-center h-9 px-2 bg-theme-surface border-b border-theme-border">
|
|
100
|
+
<div className="flex items-center gap-1 flex-1 overflow-x-auto">
|
|
101
|
+
{tabs.map(tab => (
|
|
102
|
+
<TabButton
|
|
103
|
+
key={tab.id}
|
|
104
|
+
tab={tab}
|
|
105
|
+
isActive={tab.id === activeTabId}
|
|
106
|
+
onSelect={() => {
|
|
107
|
+
setActiveTab(tab.id)
|
|
108
|
+
if (!isExpanded) toggleExpanded()
|
|
109
|
+
}}
|
|
110
|
+
onClose={() => removeTab(tab.id)}
|
|
111
|
+
/>
|
|
112
|
+
))}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<div className="flex items-center gap-1 ml-2">
|
|
116
|
+
{tabs.length > 1 && (
|
|
117
|
+
<button
|
|
118
|
+
onClick={closeAll}
|
|
119
|
+
className="p-1 text-theme-text-tertiary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
|
|
120
|
+
title="Close all"
|
|
121
|
+
>
|
|
122
|
+
<Trash2 className="w-3.5 h-3.5" />
|
|
123
|
+
</button>
|
|
124
|
+
)}
|
|
125
|
+
{isExpanded && (
|
|
126
|
+
<button
|
|
127
|
+
onClick={toggleMaximized}
|
|
128
|
+
className="p-1 text-theme-text-tertiary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
|
|
129
|
+
title={isMaximized ? 'Restore (Esc)' : 'Maximize'}
|
|
130
|
+
>
|
|
131
|
+
{isMaximized ? (
|
|
132
|
+
<Minimize2 className="w-3.5 h-3.5" />
|
|
133
|
+
) : (
|
|
134
|
+
<Maximize2 className="w-3.5 h-3.5" />
|
|
135
|
+
)}
|
|
136
|
+
</button>
|
|
137
|
+
)}
|
|
138
|
+
<button
|
|
139
|
+
onClick={() => { if (isMaximized) setIsMaximized(false); toggleExpanded() }}
|
|
140
|
+
className="p-1 text-theme-text-tertiary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
|
|
141
|
+
title={isExpanded ? 'Collapse' : 'Expand'}
|
|
142
|
+
>
|
|
143
|
+
{isExpanded ? (
|
|
144
|
+
<ChevronDown className="w-4 h-4" />
|
|
145
|
+
) : (
|
|
146
|
+
<ChevronUp className="w-4 h-4" />
|
|
147
|
+
)}
|
|
148
|
+
</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{isExpanded && (
|
|
153
|
+
<div className="flex-1 overflow-hidden w-full relative">
|
|
154
|
+
{tabs.map(tab => (
|
|
155
|
+
<div
|
|
156
|
+
key={tab.id}
|
|
157
|
+
className={tab.id === activeTabId ? 'absolute inset-0' : 'absolute inset-0 invisible'}
|
|
158
|
+
>
|
|
159
|
+
{renderTabContent(tab, tab.id === activeTabId)}
|
|
160
|
+
</div>
|
|
161
|
+
))}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function TabButton({
|
|
169
|
+
tab,
|
|
170
|
+
isActive,
|
|
171
|
+
onSelect,
|
|
172
|
+
onClose,
|
|
173
|
+
}: {
|
|
174
|
+
tab: DockTab
|
|
175
|
+
isActive: boolean
|
|
176
|
+
onSelect: () => void
|
|
177
|
+
onClose: () => void
|
|
178
|
+
}) {
|
|
179
|
+
const Icon = tab.type === 'terminal' ? Terminal : tab.type === 'workload-logs' ? Layers : FileText
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div
|
|
183
|
+
className={clsx(
|
|
184
|
+
'flex items-center gap-1.5 px-2 py-1 rounded text-xs cursor-pointer group',
|
|
185
|
+
isActive
|
|
186
|
+
? 'bg-theme-elevated text-theme-text-primary'
|
|
187
|
+
: 'text-theme-text-tertiary hover:text-theme-text-primary hover:bg-theme-elevated/50'
|
|
188
|
+
)}
|
|
189
|
+
onClick={onSelect}
|
|
190
|
+
>
|
|
191
|
+
<Icon className="w-3.5 h-3.5" />
|
|
192
|
+
<span className="truncate max-w-[120px]">{tab.title}</span>
|
|
193
|
+
<button
|
|
194
|
+
onClick={(e) => {
|
|
195
|
+
e.stopPropagation()
|
|
196
|
+
onClose()
|
|
197
|
+
}}
|
|
198
|
+
className="p-0.5 rounded opacity-0 group-hover:opacity-100 hover:bg-theme-hover"
|
|
199
|
+
>
|
|
200
|
+
<X className="w-3 h-3" />
|
|
201
|
+
</button>
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useCallback, useRef, useEffect, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export type DockTabType = 'terminal' | 'logs' | 'workload-logs'
|
|
4
|
+
|
|
5
|
+
export interface DockTab {
|
|
6
|
+
id: string
|
|
7
|
+
type: DockTabType
|
|
8
|
+
title: string
|
|
9
|
+
// Common app context (optional — app-specific, used by tab content components)
|
|
10
|
+
orgId?: string
|
|
11
|
+
clusterId?: string
|
|
12
|
+
clusterName?: string
|
|
13
|
+
// Terminal / logs props
|
|
14
|
+
namespace?: string
|
|
15
|
+
podName?: string
|
|
16
|
+
containerName?: string
|
|
17
|
+
containers?: string[]
|
|
18
|
+
// Workload logs props
|
|
19
|
+
workloadKind?: string
|
|
20
|
+
workloadName?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface DockContextValue {
|
|
24
|
+
tabs: DockTab[]
|
|
25
|
+
activeTabId: string | null
|
|
26
|
+
isExpanded: boolean
|
|
27
|
+
addTab: (tab: Omit<DockTab, 'id'>) => string
|
|
28
|
+
removeTab: (id: string) => void
|
|
29
|
+
setActiveTab: (id: string) => void
|
|
30
|
+
toggleExpanded: () => void
|
|
31
|
+
setExpanded: (expanded: boolean) => void
|
|
32
|
+
closeAll: () => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DockContext = createContext<DockContextValue | null>(null)
|
|
36
|
+
|
|
37
|
+
let tabIdCounter = 0
|
|
38
|
+
|
|
39
|
+
export function DockProvider({ children }: { children: ReactNode }) {
|
|
40
|
+
const [tabs, setTabs] = useState<DockTab[]>([])
|
|
41
|
+
const [activeTabId, setActiveTabId] = useState<string | null>(null)
|
|
42
|
+
const [isExpanded, setIsExpanded] = useState(false)
|
|
43
|
+
// Keep a ref to the latest tabs for deduplication without stale-closure issues
|
|
44
|
+
const tabsRef = useRef<DockTab[]>(tabs)
|
|
45
|
+
useEffect(() => { tabsRef.current = tabs }, [tabs])
|
|
46
|
+
|
|
47
|
+
const addTab = useCallback((tabData: Omit<DockTab, 'id'>) => {
|
|
48
|
+
const existingTab = tabsRef.current.find(t => {
|
|
49
|
+
if (t.type !== tabData.type) return false
|
|
50
|
+
// Include orgId + clusterId so same-named pods/workloads on different clusters don't deduplicate
|
|
51
|
+
if (t.orgId !== tabData.orgId || t.clusterId !== tabData.clusterId) return false
|
|
52
|
+
if (t.type === 'workload-logs') {
|
|
53
|
+
return t.namespace === tabData.namespace &&
|
|
54
|
+
t.workloadKind === tabData.workloadKind &&
|
|
55
|
+
t.workloadName === tabData.workloadName
|
|
56
|
+
}
|
|
57
|
+
return t.namespace === tabData.namespace &&
|
|
58
|
+
t.podName === tabData.podName &&
|
|
59
|
+
t.containerName === tabData.containerName
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
if (existingTab) {
|
|
63
|
+
setActiveTabId(existingTab.id)
|
|
64
|
+
setIsExpanded(true)
|
|
65
|
+
return existingTab.id
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const id = `dock-tab-${++tabIdCounter}`
|
|
69
|
+
const newTab: DockTab = { ...tabData, id }
|
|
70
|
+
|
|
71
|
+
setTabs(prev => [...prev, newTab])
|
|
72
|
+
setActiveTabId(id)
|
|
73
|
+
setIsExpanded(true)
|
|
74
|
+
|
|
75
|
+
return id
|
|
76
|
+
}, [])
|
|
77
|
+
|
|
78
|
+
const removeTab = useCallback((id: string) => {
|
|
79
|
+
setTabs(prev => {
|
|
80
|
+
const newTabs = prev.filter(t => t.id !== id)
|
|
81
|
+
|
|
82
|
+
if (activeTabId === id && newTabs.length > 0) {
|
|
83
|
+
setActiveTabId(newTabs[newTabs.length - 1].id)
|
|
84
|
+
} else if (newTabs.length === 0) {
|
|
85
|
+
setActiveTabId(null)
|
|
86
|
+
setIsExpanded(false)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return newTabs
|
|
90
|
+
})
|
|
91
|
+
}, [activeTabId])
|
|
92
|
+
|
|
93
|
+
const setActiveTab = useCallback((id: string) => {
|
|
94
|
+
setActiveTabId(id)
|
|
95
|
+
}, [])
|
|
96
|
+
|
|
97
|
+
const toggleExpanded = useCallback(() => {
|
|
98
|
+
setIsExpanded(prev => !prev)
|
|
99
|
+
}, [])
|
|
100
|
+
|
|
101
|
+
const closeAll = useCallback(() => {
|
|
102
|
+
setTabs([])
|
|
103
|
+
setActiveTabId(null)
|
|
104
|
+
setIsExpanded(false)
|
|
105
|
+
}, [])
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<DockContext.Provider value={{
|
|
109
|
+
tabs,
|
|
110
|
+
activeTabId,
|
|
111
|
+
isExpanded,
|
|
112
|
+
addTab,
|
|
113
|
+
removeTab,
|
|
114
|
+
setActiveTab,
|
|
115
|
+
toggleExpanded,
|
|
116
|
+
setExpanded: setIsExpanded,
|
|
117
|
+
closeAll,
|
|
118
|
+
}}>
|
|
119
|
+
{children}
|
|
120
|
+
</DockContext.Provider>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function useDock() {
|
|
125
|
+
const context = useContext(DockContext)
|
|
126
|
+
if (!context) {
|
|
127
|
+
throw new Error('useDock must be used within a DockProvider')
|
|
128
|
+
}
|
|
129
|
+
return context
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Convenience hooks for adding specific tab types
|
|
133
|
+
|
|
134
|
+
export function useOpenTerminal() {
|
|
135
|
+
const { addTab } = useDock()
|
|
136
|
+
|
|
137
|
+
return (opts: {
|
|
138
|
+
namespace: string
|
|
139
|
+
podName: string
|
|
140
|
+
containerName: string
|
|
141
|
+
containers: string[]
|
|
142
|
+
orgId?: string
|
|
143
|
+
clusterId?: string
|
|
144
|
+
clusterName?: string
|
|
145
|
+
}) => {
|
|
146
|
+
addTab({
|
|
147
|
+
type: 'terminal',
|
|
148
|
+
title: `${opts.podName}/${opts.containerName}`,
|
|
149
|
+
namespace: opts.namespace,
|
|
150
|
+
podName: opts.podName,
|
|
151
|
+
containerName: opts.containerName,
|
|
152
|
+
containers: opts.containers,
|
|
153
|
+
orgId: opts.orgId,
|
|
154
|
+
clusterId: opts.clusterId,
|
|
155
|
+
clusterName: opts.clusterName,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function useOpenLogs() {
|
|
161
|
+
const { addTab } = useDock()
|
|
162
|
+
|
|
163
|
+
return (opts: {
|
|
164
|
+
namespace: string
|
|
165
|
+
podName: string
|
|
166
|
+
containers: string[]
|
|
167
|
+
containerName?: string
|
|
168
|
+
orgId?: string
|
|
169
|
+
clusterId?: string
|
|
170
|
+
clusterName?: string
|
|
171
|
+
}) => {
|
|
172
|
+
const title = opts.containerName
|
|
173
|
+
? `${opts.podName}/${opts.containerName}`
|
|
174
|
+
: opts.podName
|
|
175
|
+
|
|
176
|
+
addTab({
|
|
177
|
+
type: 'logs',
|
|
178
|
+
title,
|
|
179
|
+
namespace: opts.namespace,
|
|
180
|
+
podName: opts.podName,
|
|
181
|
+
containerName: opts.containerName,
|
|
182
|
+
containers: opts.containers,
|
|
183
|
+
orgId: opts.orgId,
|
|
184
|
+
clusterId: opts.clusterId,
|
|
185
|
+
clusterName: opts.clusterName,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function useOpenWorkloadLogs() {
|
|
191
|
+
const { addTab } = useDock()
|
|
192
|
+
|
|
193
|
+
return (opts: {
|
|
194
|
+
namespace: string
|
|
195
|
+
workloadKind: string
|
|
196
|
+
workloadName: string
|
|
197
|
+
orgId?: string
|
|
198
|
+
clusterId?: string
|
|
199
|
+
clusterName?: string
|
|
200
|
+
}) => {
|
|
201
|
+
addTab({
|
|
202
|
+
type: 'workload-logs',
|
|
203
|
+
title: `${opts.workloadName} logs`,
|
|
204
|
+
namespace: opts.namespace,
|
|
205
|
+
workloadKind: opts.workloadKind,
|
|
206
|
+
workloadName: opts.workloadName,
|
|
207
|
+
orgId: opts.orgId,
|
|
208
|
+
clusterId: opts.clusterId,
|
|
209
|
+
clusterName: opts.clusterName,
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
}
|