@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.
Files changed (45) hide show
  1. package/package.json +3 -3
  2. package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -3
  3. package/src/components/dock/BottomDock.tsx +24 -17
  4. package/src/components/dock/DockContext.tsx +39 -0
  5. package/src/components/gitops/index.ts +4 -0
  6. package/src/components/gitops/insights/GitOpsInsightViews.tsx +1456 -0
  7. package/src/components/gitops/insights/index.ts +6 -0
  8. package/src/components/gitops/insights/insights-helpers.test.ts +98 -0
  9. package/src/components/gitops/insights/insights-helpers.ts +99 -0
  10. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +799 -0
  11. package/src/components/gitops/tree/index.ts +4 -0
  12. package/src/components/gitops/tree/tree-helpers.ts +42 -0
  13. package/src/components/resources/ResourcesView.tsx +71 -18
  14. package/src/components/resources/index.ts +1 -1
  15. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -1
  16. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -1
  17. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -1
  18. package/src/components/resources/renderers/PodRenderer.tsx +4 -3
  19. package/src/components/resources/renderers/SecretRenderer.tsx +4 -10
  20. package/src/components/shared/EditableYamlView.tsx +28 -17
  21. package/src/components/shared/ManagedByChip.tsx +45 -0
  22. package/src/components/shared/index.ts +1 -0
  23. package/src/components/timeline/TimelineList.tsx +3 -3
  24. package/src/components/topology/TopologyGraph.tsx +3 -2
  25. package/src/components/ui/Tooltip.tsx +10 -1
  26. package/src/components/ui/drawer-components.tsx +1 -1
  27. package/src/components/workload/ResourceDetailDrawer.tsx +5 -3
  28. package/src/components/workload/WorkloadView.tsx +66 -0
  29. package/src/hooks/useKeyboardShortcuts.tsx +3 -2
  30. package/src/index.ts +3 -0
  31. package/src/types/core.ts +19 -2
  32. package/src/types/gitops-insights.ts +193 -0
  33. package/src/types/gitops-tree.ts +57 -0
  34. package/src/types/index.ts +2 -0
  35. package/src/utils/badge-colors.ts +6 -1
  36. package/src/utils/format.ts +28 -0
  37. package/src/utils/gitops-owner.test.ts +136 -0
  38. package/src/utils/gitops-owner.ts +92 -0
  39. package/src/utils/gitops-route.test.ts +78 -0
  40. package/src/utils/gitops-route.ts +104 -0
  41. package/src/utils/index.ts +2 -0
  42. package/src/utils/navigation.ts +14 -0
  43. package/src/utils/resource-hierarchy.ts +47 -3
  44. package/src/utils/yaml.test.ts +101 -0
  45. 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.5.13",
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.4",
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.8.3"
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. The xl tier (~400px) fits names up to ~30 chars in
68
- // full comfortably covering parsed cluster names from any provider.
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 { useState, useCallback, useRef, useEffect, ReactNode } from 'react'
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 = false
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
- setIsMaximized(prev => !prev)
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') setIsMaximized(false)
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(100vh - ${MAXIMIZED_TOP_OFFSET}px)` : height
109
+ const effectiveHeight = !isExpanded ? 36 : isMaximized ? `calc(100% - ${MAXIMIZED_TOP_OFFSET}px)` : height
109
110
 
110
111
  return (
111
112
  <div
112
- className="fixed bottom-0 right-0 bg-theme-base border-t border-theme-border flex flex-col z-40 overflow-hidden"
113
- style={{ height: effectiveHeight, left: leftOffset, transition: `height ${DURATION_DOCK}ms cubic-bezier(0.4, 0, 0.2, 1), left ${DURATION_DOCK}ms ease-out` }}
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) setIsMaximized(false); toggleExpanded() }}
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'