@skyhook-io/k8s-ui 1.5.13 → 1.6.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 +3 -3
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -3
- package/src/components/dock/BottomDock.tsx +24 -17
- package/src/components/dock/DockContext.tsx +39 -0
- package/src/components/gitops/index.ts +4 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +1456 -0
- package/src/components/gitops/insights/index.ts +6 -0
- package/src/components/gitops/insights/insights-helpers.test.ts +98 -0
- package/src/components/gitops/insights/insights-helpers.ts +99 -0
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +799 -0
- package/src/components/gitops/tree/index.ts +4 -0
- package/src/components/gitops/tree/tree-helpers.ts +42 -0
- package/src/components/resources/ResourcesView.tsx +71 -18
- package/src/components/resources/index.ts +1 -1
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -1
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -1
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -1
- package/src/components/resources/renderers/PodRenderer.tsx +4 -3
- package/src/components/resources/renderers/SecretRenderer.tsx +4 -10
- package/src/components/shared/EditableYamlView.tsx +28 -17
- package/src/components/shared/ManagedByChip.tsx +45 -0
- package/src/components/shared/index.ts +1 -0
- package/src/components/timeline/TimelineList.tsx +3 -3
- package/src/components/topology/TopologyGraph.tsx +3 -2
- package/src/components/ui/Tooltip.tsx +10 -1
- package/src/components/ui/drawer-components.tsx +1 -1
- package/src/components/workload/ResourceDetailDrawer.tsx +5 -3
- package/src/components/workload/WorkloadView.tsx +66 -0
- package/src/hooks/useKeyboardShortcuts.tsx +3 -2
- package/src/index.ts +3 -0
- package/src/types/core.ts +19 -2
- package/src/types/gitops-insights.ts +193 -0
- package/src/types/gitops-tree.ts +57 -0
- package/src/types/index.ts +2 -0
- package/src/utils/badge-colors.ts +6 -1
- package/src/utils/format.ts +28 -0
- package/src/utils/gitops-owner.test.ts +136 -0
- package/src/utils/gitops-owner.ts +92 -0
- package/src/utils/gitops-route.test.ts +78 -0
- package/src/utils/gitops-route.ts +104 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/navigation.ts +14 -0
- package/src/utils/resource-hierarchy.ts +47 -3
- package/src/utils/yaml.test.ts +101 -0
- package/src/utils/yaml.ts +26 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyhook-io/k8s-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/skyhook-io/radar",
|
|
@@ -86,10 +86,10 @@
|
|
|
86
86
|
"diff": "^9.0.0",
|
|
87
87
|
"elkjs": "^0.11.1",
|
|
88
88
|
"lucide-react": "^1.12.0",
|
|
89
|
-
"react": "^19.2.
|
|
89
|
+
"react": "^19.2.6",
|
|
90
90
|
"react-dom": "^19.2.4",
|
|
91
91
|
"typescript": "^6.0.2",
|
|
92
92
|
"vitest": "^4.1.5",
|
|
93
|
-
"yaml": "^2.
|
|
93
|
+
"yaml": "^2.9.0"
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -64,9 +64,8 @@ export interface ClusterSwitcherProps {
|
|
|
64
64
|
// horizontal-real-estate guard rather than a readability one. The cap
|
|
65
65
|
// grows with viewport so wide screens (where there's no real estate
|
|
66
66
|
// pressure) show full cluster names rather than middle-truncating
|
|
67
|
-
// pointlessly.
|
|
68
|
-
|
|
69
|
-
const TRIGGER_NAME_MAX_WIDTH = 'max-w-[160px] sm:max-w-[260px] xl:max-w-[400px]'
|
|
67
|
+
// pointlessly.
|
|
68
|
+
const TRIGGER_NAME_MAX_WIDTH = 'max-w-[140px] sm:max-w-[220px] xl:max-w-[340px]'
|
|
70
69
|
|
|
71
70
|
export const ClusterSwitcher = forwardRef<ClusterSwitcherHandle, ClusterSwitcherProps>(({
|
|
72
71
|
currentId,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useRef, useEffect, ReactNode } from 'react'
|
|
2
2
|
import { DURATION_DOCK } from '../../utils/animation'
|
|
3
3
|
import { X, ChevronDown, ChevronUp, Terminal, FileText, Trash2, Layers, Maximize2, Minimize2, Activity } from 'lucide-react'
|
|
4
4
|
import { clsx } from 'clsx'
|
|
@@ -6,7 +6,6 @@ import { useDock, DockTab } from './DockContext'
|
|
|
6
6
|
import { useRegisterShortcuts } from '../../hooks/useKeyboardShortcuts'
|
|
7
7
|
|
|
8
8
|
const MIN_HEIGHT = 200
|
|
9
|
-
const DEFAULT_HEIGHT = 400
|
|
10
9
|
const MAX_HEIGHT_RATIO = 0.7
|
|
11
10
|
const MAXIMIZED_TOP_OFFSET = 48
|
|
12
11
|
|
|
@@ -21,28 +20,27 @@ interface BottomDockProps {
|
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
export function BottomDock({ renderTabContent, renderTabHeaderExtra, leftOffset: leftOffsetProp, defaultHeight }: BottomDockProps) {
|
|
24
|
-
const { tabs, activeTabId, isExpanded, leftOffset: leftOffsetCtx, removeTab, setActiveTab, toggleExpanded, closeAll } = useDock()
|
|
23
|
+
const { tabs, activeTabId, isExpanded, leftOffset: leftOffsetCtx, height, isMaximized, isResizing, setHeight, setMaximized, setResizing, removeTab, setActiveTab, toggleExpanded, closeAll } = useDock()
|
|
25
24
|
const leftOffset = leftOffsetProp ?? leftOffsetCtx
|
|
26
|
-
const [height, setHeight] = useState(defaultHeight ?? DEFAULT_HEIGHT)
|
|
27
25
|
const prevDefaultHeight = useRef(defaultHeight)
|
|
28
26
|
useEffect(() => {
|
|
29
27
|
if (defaultHeight != null && defaultHeight !== prevDefaultHeight.current) {
|
|
30
28
|
setHeight(defaultHeight)
|
|
31
29
|
}
|
|
32
30
|
prevDefaultHeight.current = defaultHeight
|
|
33
|
-
}, [defaultHeight])
|
|
34
|
-
const [isMaximized, setIsMaximized] = useState(false)
|
|
31
|
+
}, [defaultHeight, setHeight])
|
|
35
32
|
const isDragging = useRef(false)
|
|
36
33
|
const startY = useRef(0)
|
|
37
34
|
const startHeight = useRef(0)
|
|
38
35
|
|
|
39
36
|
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
|
40
37
|
isDragging.current = true
|
|
38
|
+
setResizing(true)
|
|
41
39
|
startY.current = e.clientY
|
|
42
40
|
startHeight.current = height
|
|
43
41
|
document.body.style.cursor = 'ns-resize'
|
|
44
42
|
document.body.style.userSelect = 'none'
|
|
45
|
-
}, [height])
|
|
43
|
+
}, [height, setResizing])
|
|
46
44
|
|
|
47
45
|
useEffect(() => {
|
|
48
46
|
const handleMouseMove = (e: MouseEvent) => {
|
|
@@ -53,7 +51,10 @@ export function BottomDock({ renderTabContent, renderTabHeaderExtra, leftOffset:
|
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
const handleMouseUp = () => {
|
|
56
|
-
isDragging.current
|
|
54
|
+
if (isDragging.current) {
|
|
55
|
+
isDragging.current = false
|
|
56
|
+
setResizing(false)
|
|
57
|
+
}
|
|
57
58
|
document.body.style.cursor = ''
|
|
58
59
|
document.body.style.userSelect = ''
|
|
59
60
|
}
|
|
@@ -65,20 +66,20 @@ export function BottomDock({ renderTabContent, renderTabHeaderExtra, leftOffset:
|
|
|
65
66
|
document.removeEventListener('mousemove', handleMouseMove)
|
|
66
67
|
document.removeEventListener('mouseup', handleMouseUp)
|
|
67
68
|
}
|
|
68
|
-
}, [])
|
|
69
|
+
}, [setHeight, setResizing])
|
|
69
70
|
|
|
70
71
|
const toggleMaximized = useCallback(() => {
|
|
71
|
-
|
|
72
|
-
}, [])
|
|
72
|
+
setMaximized(!isMaximized)
|
|
73
|
+
}, [isMaximized, setMaximized])
|
|
73
74
|
|
|
74
75
|
useEffect(() => {
|
|
75
76
|
if (!isMaximized) return
|
|
76
77
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
77
|
-
if (e.key === 'Escape')
|
|
78
|
+
if (e.key === 'Escape') setMaximized(false)
|
|
78
79
|
}
|
|
79
80
|
window.addEventListener('keydown', handleKeyDown)
|
|
80
81
|
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
81
|
-
}, [isMaximized])
|
|
82
|
+
}, [isMaximized, setMaximized])
|
|
82
83
|
|
|
83
84
|
useRegisterShortcuts([
|
|
84
85
|
{
|
|
@@ -105,12 +106,18 @@ export function BottomDock({ renderTabContent, renderTabHeaderExtra, leftOffset:
|
|
|
105
106
|
return null
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
const effectiveHeight = !isExpanded ? 36 : isMaximized ? `calc(
|
|
109
|
+
const effectiveHeight = !isExpanded ? 36 : isMaximized ? `calc(100% - ${MAXIMIZED_TOP_OFFSET}px)` : height
|
|
109
110
|
|
|
110
111
|
return (
|
|
111
112
|
<div
|
|
112
|
-
className="
|
|
113
|
-
style={{
|
|
113
|
+
className="absolute bottom-0 right-0 bg-theme-base border-t border-theme-border flex flex-col z-40 overflow-hidden"
|
|
114
|
+
style={{
|
|
115
|
+
height: effectiveHeight,
|
|
116
|
+
left: leftOffset,
|
|
117
|
+
transition: isResizing
|
|
118
|
+
? `left ${DURATION_DOCK}ms ease-out`
|
|
119
|
+
: `height ${DURATION_DOCK}ms cubic-bezier(0.4, 0, 0.2, 1), left ${DURATION_DOCK}ms ease-out`,
|
|
120
|
+
}}
|
|
114
121
|
>
|
|
115
122
|
{isExpanded && !isMaximized && (
|
|
116
123
|
<div
|
|
@@ -165,7 +172,7 @@ export function BottomDock({ renderTabContent, renderTabHeaderExtra, leftOffset:
|
|
|
165
172
|
</button>
|
|
166
173
|
)}
|
|
167
174
|
<button
|
|
168
|
-
onClick={() => { if (isMaximized)
|
|
175
|
+
onClick={() => { if (isMaximized) setMaximized(false); toggleExpanded() }}
|
|
169
176
|
className="p-1 text-theme-text-tertiary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
|
|
170
177
|
title={isExpanded ? 'Collapse' : 'Expand'}
|
|
171
178
|
>
|
|
@@ -29,12 +29,18 @@ export interface DockContextValue {
|
|
|
29
29
|
activeTabId: string | null
|
|
30
30
|
isExpanded: boolean
|
|
31
31
|
leftOffset: number
|
|
32
|
+
height: number
|
|
33
|
+
isMaximized: boolean
|
|
34
|
+
isResizing: boolean
|
|
32
35
|
addTab: (tab: Omit<DockTab, 'id'>) => string
|
|
33
36
|
removeTab: (id: string) => void
|
|
34
37
|
setActiveTab: (id: string) => void
|
|
35
38
|
toggleExpanded: () => void
|
|
36
39
|
setExpanded: (expanded: boolean) => void
|
|
37
40
|
setLeftOffset: (offset: number) => void
|
|
41
|
+
setHeight: (height: number) => void
|
|
42
|
+
setMaximized: (maximized: boolean) => void
|
|
43
|
+
setResizing: (resizing: boolean) => void
|
|
38
44
|
closeAll: () => void
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -42,11 +48,17 @@ const DockContext = createContext<DockContextValue | null>(null)
|
|
|
42
48
|
|
|
43
49
|
let tabIdCounter = 0
|
|
44
50
|
|
|
51
|
+
const DOCK_DEFAULT_HEIGHT = 400
|
|
52
|
+
const DOCK_COLLAPSED_HEIGHT = 36
|
|
53
|
+
|
|
45
54
|
export function DockProvider({ children }: { children: ReactNode }) {
|
|
46
55
|
const [tabs, setTabs] = useState<DockTab[]>([])
|
|
47
56
|
const [activeTabId, setActiveTabId] = useState<string | null>(null)
|
|
48
57
|
const [isExpanded, setIsExpanded] = useState(false)
|
|
49
58
|
const [leftOffset, setLeftOffset] = useState(0)
|
|
59
|
+
const [height, setHeight] = useState(DOCK_DEFAULT_HEIGHT)
|
|
60
|
+
const [isMaximized, setIsMaximized] = useState(false)
|
|
61
|
+
const [isResizing, setIsResizing] = useState(false)
|
|
50
62
|
// Keep a ref to the latest tabs for deduplication without stale-closure issues
|
|
51
63
|
const tabsRef = useRef<DockTab[]>(tabs)
|
|
52
64
|
useEffect(() => { tabsRef.current = tabs }, [tabs])
|
|
@@ -126,12 +138,18 @@ export function DockProvider({ children }: { children: ReactNode }) {
|
|
|
126
138
|
activeTabId,
|
|
127
139
|
isExpanded,
|
|
128
140
|
leftOffset,
|
|
141
|
+
height,
|
|
142
|
+
isMaximized,
|
|
143
|
+
isResizing,
|
|
129
144
|
addTab,
|
|
130
145
|
removeTab,
|
|
131
146
|
setActiveTab,
|
|
132
147
|
toggleExpanded,
|
|
133
148
|
setExpanded: setIsExpanded,
|
|
134
149
|
setLeftOffset,
|
|
150
|
+
setHeight,
|
|
151
|
+
setMaximized: setIsMaximized,
|
|
152
|
+
setResizing: setIsResizing,
|
|
135
153
|
closeAll,
|
|
136
154
|
}}>
|
|
137
155
|
{children}
|
|
@@ -147,6 +165,27 @@ export function useDock() {
|
|
|
147
165
|
return context
|
|
148
166
|
}
|
|
149
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Reserved vertical space at the bottom of the viewport for the dock, in px.
|
|
170
|
+
* Drawers and main-content spacers subtract this so they don't sit behind the dock.
|
|
171
|
+
* Maximized returns 0 — the dock covers everything by design.
|
|
172
|
+
*
|
|
173
|
+
* Safe to call without a DockProvider ancestor (returns 0) — the shared
|
|
174
|
+
* @skyhook/k8s-ui package exports drawers that may be consumed without a dock.
|
|
175
|
+
*/
|
|
176
|
+
export function useDockReservedHeight(): number {
|
|
177
|
+
const context = useContext(DockContext)
|
|
178
|
+
if (!context) return 0
|
|
179
|
+
const { tabs, isExpanded, isMaximized, height } = context
|
|
180
|
+
if (tabs.length === 0) return 0
|
|
181
|
+
// Order matches BottomDock's effectiveHeight: collapsed tab bar wins over
|
|
182
|
+
// maximized so the (maximized=true, expanded=false) state — reachable by
|
|
183
|
+
// Cmd+J / Ctrl+` while maximized — still reserves the 36px tab bar height.
|
|
184
|
+
if (!isExpanded) return DOCK_COLLAPSED_HEIGHT
|
|
185
|
+
if (isMaximized) return 0
|
|
186
|
+
return height
|
|
187
|
+
}
|
|
188
|
+
|
|
150
189
|
// Convenience hooks for adding specific tab types
|
|
151
190
|
|
|
152
191
|
export function useOpenTerminal() {
|
|
@@ -2,3 +2,7 @@ export { GitOpsStatusBadge, SyncStatusBadge, HealthStatusBadge } from './GitOpsS
|
|
|
2
2
|
export { SyncCountdown, IntervalDisplay } from './SyncCountdown'
|
|
3
3
|
export { ManagedResourcesList, InventoryCount } from './ManagedResourcesList'
|
|
4
4
|
export { GitOpsActions, SyncButton, SuspendToggle } from './GitOpsActions'
|
|
5
|
+
export { GitOpsTreeGraph } from './tree'
|
|
6
|
+
export { gitOpsFilterSet, hasGitOpsTreeFilters, matchesGitOpsTreeFilters } from './tree'
|
|
7
|
+
export type { GitOpsTreeFilters, GitOpsTreePreset } from './tree'
|
|
8
|
+
export * from './insights'
|