@skyhook-io/radar-app 0.1.1

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 (251) hide show
  1. package/README.md +67 -0
  2. package/package.json +80 -0
  3. package/src/App.tsx +1538 -0
  4. package/src/RadarApp.tsx +145 -0
  5. package/src/api/apiResources.ts +28 -0
  6. package/src/api/client.ts +2583 -0
  7. package/src/api/config.ts +116 -0
  8. package/src/api/traffic.ts +139 -0
  9. package/src/components/ConnectionErrorView.tsx +272 -0
  10. package/src/components/ContextSwitcher.tsx +481 -0
  11. package/src/components/DebugOverlay.tsx +94 -0
  12. package/src/components/UserMenu.tsx +87 -0
  13. package/src/components/audit/AuditSettingsDialog.tsx +162 -0
  14. package/src/components/audit/AuditView.tsx +123 -0
  15. package/src/components/cost/CostTrendChart.tsx +388 -0
  16. package/src/components/cost/CostView.tsx +545 -0
  17. package/src/components/dock/BottomDock.tsx +96 -0
  18. package/src/components/dock/DockContext.tsx +11 -0
  19. package/src/components/dock/LocalTerminalTab.tsx +22 -0
  20. package/src/components/dock/LogsTab.tsx +26 -0
  21. package/src/components/dock/NodeTerminalTab.tsx +50 -0
  22. package/src/components/dock/TerminalTab.tsx +42 -0
  23. package/src/components/dock/TrafficFlowListTab.tsx +18 -0
  24. package/src/components/dock/WorkloadLogsTab.tsx +23 -0
  25. package/src/components/dock/index.ts +2 -0
  26. package/src/components/gitops/GitOpsActions.tsx +1 -0
  27. package/src/components/gitops/GitOpsStatusBadge.tsx +1 -0
  28. package/src/components/gitops/ManagedResourcesList.tsx +1 -0
  29. package/src/components/gitops/SyncCountdown.tsx +1 -0
  30. package/src/components/gitops/index.ts +4 -0
  31. package/src/components/helm/ChartBrowser.tsx +580 -0
  32. package/src/components/helm/HelmReleaseDrawer.tsx +774 -0
  33. package/src/components/helm/HelmView.tsx +475 -0
  34. package/src/components/helm/InstallWizard.tsx +1060 -0
  35. package/src/components/helm/ManifestDiffViewer.tsx +91 -0
  36. package/src/components/helm/ManifestViewer.tsx +61 -0
  37. package/src/components/helm/OwnedResources.tsx +465 -0
  38. package/src/components/helm/RevisionHistory.tsx +167 -0
  39. package/src/components/helm/ValuesDiffPreview.tsx +190 -0
  40. package/src/components/helm/ValuesViewer.tsx +365 -0
  41. package/src/components/helm/helm-utils.ts +37 -0
  42. package/src/components/home/ActivitySummary.tsx +262 -0
  43. package/src/components/home/CertificateHealthCard.tsx +105 -0
  44. package/src/components/home/ClusterHealthCard.tsx +483 -0
  45. package/src/components/home/CostCard.tsx +112 -0
  46. package/src/components/home/HealthRing.tsx +1 -0
  47. package/src/components/home/HelmSummary.tsx +129 -0
  48. package/src/components/home/HomeView.tsx +224 -0
  49. package/src/components/home/MCPSetupDialog.tsx +417 -0
  50. package/src/components/home/NetworkPolicyCoverageCard.tsx +109 -0
  51. package/src/components/home/TopologyPreview.tsx +219 -0
  52. package/src/components/home/TrafficSummary.tsx +154 -0
  53. package/src/components/logs/JsonLogLine.tsx +1 -0
  54. package/src/components/logs/LogCore.tsx +2 -0
  55. package/src/components/logs/LogsViewer.tsx +44 -0
  56. package/src/components/logs/WorkloadLogsViewer.tsx +40 -0
  57. package/src/components/logs/useLogBuffer.ts +2 -0
  58. package/src/components/logs/useLogSearch.ts +1 -0
  59. package/src/components/portforward/PortForwardButton.tsx +375 -0
  60. package/src/components/portforward/PortForwardManager.tsx +871 -0
  61. package/src/components/resource/PrometheusCharts.tsx +687 -0
  62. package/src/components/resource-drawer/ResourceDrawer.tsx +214 -0
  63. package/src/components/resources/ImageFilesystemModal.tsx +745 -0
  64. package/src/components/resources/PodFilesystemModal.tsx +407 -0
  65. package/src/components/resources/ResourceDetailDrawer.tsx +43 -0
  66. package/src/components/resources/ResourcesView.tsx +190 -0
  67. package/src/components/resources/drawer-components.tsx +1 -0
  68. package/src/components/resources/file-browser-utils.ts +35 -0
  69. package/src/components/resources/renderers/AlertRenderer.tsx +1 -0
  70. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +17 -0
  71. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +1 -0
  72. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +1 -0
  73. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +1 -0
  74. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +1 -0
  75. package/src/components/resources/renderers/CertificateRenderer.tsx +1 -0
  76. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +1 -0
  77. package/src/components/resources/renderers/ChallengeRenderer.tsx +1 -0
  78. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +1 -0
  79. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +1 -0
  80. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +1 -0
  81. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +1 -0
  82. package/src/components/resources/renderers/ConfigMapRenderer.tsx +1 -0
  83. package/src/components/resources/renderers/CronJobRenderer.tsx +1 -0
  84. package/src/components/resources/renderers/EventRenderer.tsx +1 -0
  85. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +1 -0
  86. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +1 -0
  87. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +1 -0
  88. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +1 -0
  89. package/src/components/resources/renderers/GatewayClassRenderer.tsx +1 -0
  90. package/src/components/resources/renderers/GatewayRenderer.tsx +1 -0
  91. package/src/components/resources/renderers/GenericRenderer.tsx +1 -0
  92. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +1 -0
  93. package/src/components/resources/renderers/HPARenderer.tsx +1 -0
  94. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +1 -0
  95. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +1 -0
  96. package/src/components/resources/renderers/IngressClassRenderer.tsx +1 -0
  97. package/src/components/resources/renderers/IngressRenderer.tsx +1 -0
  98. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +1 -0
  99. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +1 -0
  100. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +1 -0
  101. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +1 -0
  102. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +1 -0
  103. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +1 -0
  104. package/src/components/resources/renderers/JobRenderer.tsx +1 -0
  105. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +1 -0
  106. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +1 -0
  107. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +1 -0
  108. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +1 -0
  109. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +1 -0
  110. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +1 -0
  111. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +1 -0
  112. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +1 -0
  113. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +1 -0
  114. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +1 -0
  115. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +1 -0
  116. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +1 -0
  117. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +1 -0
  118. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +1 -0
  119. package/src/components/resources/renderers/KustomizationRenderer.tsx +1 -0
  120. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +1 -0
  121. package/src/components/resources/renderers/LeaseRenderer.tsx +1 -0
  122. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +1 -0
  123. package/src/components/resources/renderers/NodeRenderer.tsx +44 -0
  124. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +1 -0
  125. package/src/components/resources/renderers/OrderRenderer.tsx +1 -0
  126. package/src/components/resources/renderers/PVCRenderer.tsx +1 -0
  127. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +1 -0
  128. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +1 -0
  129. package/src/components/resources/renderers/PodMonitorRenderer.tsx +1 -0
  130. package/src/components/resources/renderers/PodRenderer.tsx +94 -0
  131. package/src/components/resources/renderers/PriorityClassRenderer.tsx +1 -0
  132. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +1 -0
  133. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +1 -0
  134. package/src/components/resources/renderers/RoleBindingRenderer.tsx +1 -0
  135. package/src/components/resources/renderers/RoleRenderer.tsx +1 -0
  136. package/src/components/resources/renderers/RolloutRenderer.tsx +1 -0
  137. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +1 -0
  138. package/src/components/resources/renderers/SbomReportRenderer.tsx +1 -0
  139. package/src/components/resources/renderers/SealedSecretRenderer.tsx +1 -0
  140. package/src/components/resources/renderers/SecretRenderer.tsx +1 -0
  141. package/src/components/resources/renderers/SecretStoreRenderer.tsx +1 -0
  142. package/src/components/resources/renderers/ServiceAccountRenderer.tsx +1 -0
  143. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +1 -0
  144. package/src/components/resources/renderers/ServiceRenderer.tsx +26 -0
  145. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +1 -0
  146. package/src/components/resources/renderers/StorageClassRenderer.tsx +1 -0
  147. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +1 -0
  148. package/src/components/resources/renderers/VPARenderer.tsx +1 -0
  149. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +1 -0
  150. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +1 -0
  151. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +1 -0
  152. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +1 -0
  153. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +1 -0
  154. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +1 -0
  155. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +1 -0
  156. package/src/components/resources/renderers/WorkflowRenderer.tsx +1 -0
  157. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +1 -0
  158. package/src/components/resources/renderers/WorkloadRenderer.tsx +52 -0
  159. package/src/components/resources/renderers/argo-cells.tsx +1 -0
  160. package/src/components/resources/renderers/certmanager-cells.tsx +1 -0
  161. package/src/components/resources/renderers/cnpg-cells.tsx +1 -0
  162. package/src/components/resources/renderers/eso-cells.tsx +1 -0
  163. package/src/components/resources/renderers/flux-cells.tsx +1 -0
  164. package/src/components/resources/renderers/index.ts +91 -0
  165. package/src/components/resources/renderers/istio-cells.tsx +1 -0
  166. package/src/components/resources/renderers/karpenter-cells.tsx +1 -0
  167. package/src/components/resources/renderers/keda-cells.tsx +1 -0
  168. package/src/components/resources/renderers/knative-cells.tsx +1 -0
  169. package/src/components/resources/renderers/kyverno-cells.tsx +1 -0
  170. package/src/components/resources/renderers/prometheus-cells.tsx +1 -0
  171. package/src/components/resources/renderers/traefik-cells.tsx +1 -0
  172. package/src/components/resources/renderers/trivy-cells.tsx +1 -0
  173. package/src/components/resources/renderers/trivy-shared.tsx +1 -0
  174. package/src/components/resources/renderers/velero-cells.tsx +1 -0
  175. package/src/components/resources/resource-utils-argo.ts +2 -0
  176. package/src/components/resources/resource-utils-certmanager.ts +2 -0
  177. package/src/components/resources/resource-utils-cnpg.ts +2 -0
  178. package/src/components/resources/resource-utils-eso.ts +2 -0
  179. package/src/components/resources/resource-utils-flux.ts +2 -0
  180. package/src/components/resources/resource-utils-istio.ts +2 -0
  181. package/src/components/resources/resource-utils-karpenter.ts +2 -0
  182. package/src/components/resources/resource-utils-keda.ts +2 -0
  183. package/src/components/resources/resource-utils-knative.ts +2 -0
  184. package/src/components/resources/resource-utils-kyverno.ts +2 -0
  185. package/src/components/resources/resource-utils-prometheus.ts +2 -0
  186. package/src/components/resources/resource-utils-traefik.ts +1 -0
  187. package/src/components/resources/resource-utils-trivy.ts +2 -0
  188. package/src/components/resources/resource-utils-velero.ts +2 -0
  189. package/src/components/resources/resource-utils.ts +5 -0
  190. package/src/components/settings/SettingsDialog.tsx +537 -0
  191. package/src/components/shared/CreateResourceDialog.tsx +17 -0
  192. package/src/components/shared/EditableYamlView.tsx +24 -0
  193. package/src/components/shared/LargeClusterNamespacePicker.tsx +70 -0
  194. package/src/components/shared/ResourceRendererDispatch.tsx +31 -0
  195. package/src/components/timeline/DiffViewer.tsx +1 -0
  196. package/src/components/timeline/TimelineList.tsx +69 -0
  197. package/src/components/timeline/TimelineSwimlanes.tsx +1308 -0
  198. package/src/components/timeline/TimelineView.tsx +157 -0
  199. package/src/components/timeline/shared.tsx +1 -0
  200. package/src/components/traffic/TrafficFilterSidebar.tsx +571 -0
  201. package/src/components/traffic/TrafficFlowList.tsx +415 -0
  202. package/src/components/traffic/TrafficFlowListContext.tsx +68 -0
  203. package/src/components/traffic/TrafficGraph.tsx +1546 -0
  204. package/src/components/traffic/TrafficView.tsx +1213 -0
  205. package/src/components/traffic/TrafficWizard.tsx +386 -0
  206. package/src/components/traffic/index.ts +3 -0
  207. package/src/components/ui/CodeViewer.tsx +8 -0
  208. package/src/components/ui/CommandPalette.tsx +460 -0
  209. package/src/components/ui/ConfirmDialog.tsx +1 -0
  210. package/src/components/ui/DiagnosticsOverlay.tsx +619 -0
  211. package/src/components/ui/ErrorBoundary.tsx +46 -0
  212. package/src/components/ui/ForceDeleteConfirmDialog.tsx +1 -0
  213. package/src/components/ui/Markdown.tsx +108 -0
  214. package/src/components/ui/MetricsChart.tsx +1 -0
  215. package/src/components/ui/NamespaceSelector.tsx +436 -0
  216. package/src/components/ui/ResourceBar.tsx +1 -0
  217. package/src/components/ui/ShortcutHelpOverlay.tsx +301 -0
  218. package/src/components/ui/Toast.tsx +1 -0
  219. package/src/components/ui/Tooltip.tsx +1 -0
  220. package/src/components/ui/UpdateNotification.tsx +299 -0
  221. package/src/components/ui/YamlEditor.tsx +1 -0
  222. package/src/components/workload/WorkloadView.tsx +532 -0
  223. package/src/context/ConnectionContext.tsx +173 -0
  224. package/src/context/ContextSwitchContext.tsx +56 -0
  225. package/src/context/NavCustomization.tsx +62 -0
  226. package/src/context/ThemeContext.tsx +97 -0
  227. package/src/contexts/CapabilitiesContext.tsx +130 -0
  228. package/src/hooks/useAnimatedUnmount.ts +1 -0
  229. package/src/hooks/useDesktopDownload.ts +41 -0
  230. package/src/hooks/useEventSource.ts +262 -0
  231. package/src/hooks/useFavorites.ts +69 -0
  232. package/src/hooks/useKeyboardShortcuts.tsx +7 -0
  233. package/src/hooks/useRefreshAnimation.ts +1 -0
  234. package/src/index.css +243 -0
  235. package/src/index.ts +17 -0
  236. package/src/main.tsx +158 -0
  237. package/src/types/gitops.ts +2 -0
  238. package/src/types.ts +3 -0
  239. package/src/utils/animation.ts +2 -0
  240. package/src/utils/badge-colors.ts +2 -0
  241. package/src/utils/context-name.ts +2 -0
  242. package/src/utils/desktop-download.ts +66 -0
  243. package/src/utils/desktop-open-folder.ts +21 -0
  244. package/src/utils/format.ts +2 -0
  245. package/src/utils/log-format.ts +12 -0
  246. package/src/utils/navigation.ts +23 -0
  247. package/src/utils/resource-hierarchy.ts +2 -0
  248. package/src/utils/resource-icons.ts +2 -0
  249. package/src/utils/skeleton-yaml.ts +2 -0
  250. package/src/utils/traffic-colors.ts +54 -0
  251. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,475 @@
1
+ import { useState, useMemo, useRef, useEffect, useCallback, forwardRef } from 'react'
2
+ import { useRefreshAnimation } from '../../hooks/useRefreshAnimation'
3
+ import { useRegisterShortcuts } from '../../hooks/useKeyboardShortcuts'
4
+ import { Package, Search, RefreshCw, ArrowUpCircle, LayoutGrid, List, Shield } from 'lucide-react'
5
+ import { clsx } from 'clsx'
6
+ import { useHelmReleases, useHelmBatchUpgradeInfo, isForbiddenError } from '../../api/client'
7
+ import type { HelmRelease, SelectedHelmRelease, UpgradeInfo, ChartSource } from '../../types'
8
+ import { getStatusColor, formatAge, truncate } from './helm-utils'
9
+ import { SEVERITY_BADGE } from '../../utils/badge-colors'
10
+ import { Tooltip } from '../ui/Tooltip'
11
+ import { ChartBrowser } from './ChartBrowser'
12
+ import { InstallWizard } from './InstallWizard'
13
+
14
+ type ViewTab = 'releases' | 'charts'
15
+
16
+ interface HelmViewProps {
17
+ namespace: string
18
+ selectedRelease?: SelectedHelmRelease | null
19
+ onReleaseClick?: (namespace: string, name: string) => void
20
+ }
21
+
22
+ export function HelmView({ namespace, selectedRelease, onReleaseClick }: HelmViewProps) {
23
+ const [activeTab, setActiveTab] = useState<ViewTab>('releases')
24
+ const [searchTerm, setSearchTerm] = useState('')
25
+ const [selectedChart, setSelectedChart] = useState<{ repo: string; chart: string; version: string; source: ChartSource } | null>(null)
26
+
27
+ const { data: releases, isLoading, error: releasesError, refetch: refetchReleases } = useHelmReleases(namespace || undefined)
28
+ const isForbidden = isForbiddenError(releasesError)
29
+
30
+ // Lazy load upgrade info after releases are loaded
31
+ const { data: upgradeInfo, isLoading: upgradeLoading, refetch: refetchUpgradeInfo } = useHelmBatchUpgradeInfo(
32
+ namespace || undefined,
33
+ Boolean(releases && releases.length > 0)
34
+ )
35
+
36
+ const [handleRefresh, isRefreshAnimating] = useRefreshAnimation(async () => {
37
+ await Promise.all([refetchReleases(), refetchUpgradeInfo()])
38
+ })
39
+
40
+ const isFullyLoaded = !isLoading && !upgradeLoading
41
+
42
+ // Filter releases by search term
43
+ const filteredReleases = useMemo(() => {
44
+ if (!releases) return []
45
+ if (!searchTerm) return releases
46
+ const term = searchTerm.toLowerCase()
47
+ return releases.filter(
48
+ (r) =>
49
+ r.name.toLowerCase().includes(term) ||
50
+ r.namespace.toLowerCase().includes(term) ||
51
+ r.chart.toLowerCase().includes(term)
52
+ )
53
+ }, [releases, searchTerm])
54
+
55
+ // Keyboard navigation state
56
+ const searchInputRef = useRef<HTMLInputElement>(null)
57
+ const highlightedRowRef = useRef<HTMLTableRowElement>(null)
58
+ const [highlightedIndex, setHighlightedIndex] = useState(-1)
59
+ const filteredReleasesCountRef = useRef(0)
60
+ filteredReleasesCountRef.current = filteredReleases.length
61
+
62
+ // Reset highlight when search changes
63
+ useEffect(() => { setHighlightedIndex(-1) }, [searchTerm])
64
+
65
+ // Scroll highlighted row into view
66
+ useEffect(() => {
67
+ if (highlightedIndex >= 0 && highlightedRowRef.current) {
68
+ highlightedRowRef.current.scrollIntoView({ block: 'nearest' })
69
+ }
70
+ }, [highlightedIndex])
71
+
72
+ // Helper: get release at highlighted index
73
+ const getHighlightedRelease = useCallback(() => {
74
+ if (highlightedIndex < 0 || highlightedIndex >= filteredReleases.length) return null
75
+ return filteredReleases[highlightedIndex]
76
+ }, [highlightedIndex, filteredReleases])
77
+
78
+ // Register Helm keyboard shortcuts
79
+ useRegisterShortcuts([
80
+ {
81
+ id: 'helm-search',
82
+ keys: '/',
83
+ description: 'Focus search',
84
+ category: 'Search',
85
+ scope: 'helm',
86
+ handler: () => searchInputRef.current?.focus(),
87
+ },
88
+ {
89
+ id: 'helm-nav-down',
90
+ keys: 'j',
91
+ description: 'Next row',
92
+ category: 'Helm',
93
+ scope: 'helm',
94
+ handler: () => setHighlightedIndex(i => {
95
+ const max = filteredReleasesCountRef.current - 1
96
+ return i < max ? i + 1 : i
97
+ }),
98
+ },
99
+ {
100
+ id: 'helm-nav-down-arrow',
101
+ keys: 'ArrowDown',
102
+ description: 'Next row',
103
+ category: 'Helm',
104
+ scope: 'helm',
105
+ handler: () => setHighlightedIndex(i => {
106
+ const max = filteredReleasesCountRef.current - 1
107
+ return i < max ? i + 1 : i
108
+ }),
109
+ },
110
+ {
111
+ id: 'helm-nav-up',
112
+ keys: 'k',
113
+ description: 'Previous row',
114
+ category: 'Helm',
115
+ scope: 'helm',
116
+ handler: () => setHighlightedIndex(i => i > 0 ? i - 1 : 0),
117
+ },
118
+ {
119
+ id: 'helm-nav-up-arrow',
120
+ keys: 'ArrowUp',
121
+ description: 'Previous row',
122
+ category: 'Helm',
123
+ scope: 'helm',
124
+ handler: () => setHighlightedIndex(i => i > 0 ? i - 1 : 0),
125
+ },
126
+ {
127
+ id: 'helm-open',
128
+ keys: 'Enter',
129
+ description: 'Open release detail',
130
+ category: 'Helm',
131
+ scope: 'helm',
132
+ handler: () => {
133
+ const release = getHighlightedRelease()
134
+ if (release) onReleaseClick?.(release.namespace, release.name)
135
+ },
136
+ enabled: highlightedIndex >= 0,
137
+ },
138
+ {
139
+ id: 'helm-clear-highlight',
140
+ keys: 'Escape',
141
+ description: 'Clear highlight / blur search',
142
+ category: 'Helm',
143
+ scope: 'helm',
144
+ handler: () => {
145
+ if (highlightedIndex >= 0) setHighlightedIndex(-1)
146
+ else searchInputRef.current?.blur()
147
+ },
148
+ },
149
+ ])
150
+
151
+ const handleChartSelect = (repo: string, chart: string, version: string, source: ChartSource) => {
152
+ setSelectedChart({ repo, chart, version, source })
153
+ }
154
+
155
+ const handleInstallSuccess = (releaseNamespace: string, releaseName: string) => {
156
+ setSelectedChart(null)
157
+ setActiveTab('releases')
158
+ refetchReleases()
159
+ // Navigate to the new release
160
+ onReleaseClick?.(releaseNamespace, releaseName)
161
+ }
162
+
163
+ return (
164
+ <div className="flex h-full w-full">
165
+ {/* Main Content */}
166
+ <div className="flex-1 flex flex-col overflow-hidden min-w-0 w-full">
167
+ {/* Tab bar */}
168
+ <div className="flex items-center gap-1 px-4 pt-3 border-b border-theme-border bg-theme-surface/50">
169
+ <button
170
+ onClick={() => setActiveTab('releases')}
171
+ className={clsx(
172
+ 'flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors',
173
+ activeTab === 'releases'
174
+ ? 'text-theme-text-primary border-blue-500'
175
+ : 'text-theme-text-secondary border-transparent hover:text-theme-text-primary hover:border-theme-border'
176
+ )}
177
+ >
178
+ <List className="w-4 h-4" />
179
+ Installed
180
+ {releases && (
181
+ <span className="badge-sm bg-theme-elevated">
182
+ {releases.length}
183
+ </span>
184
+ )}
185
+ </button>
186
+ <button
187
+ onClick={() => setActiveTab('charts')}
188
+ className={clsx(
189
+ 'flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors',
190
+ activeTab === 'charts'
191
+ ? 'text-theme-text-primary border-blue-500'
192
+ : 'text-theme-text-secondary border-transparent hover:text-theme-text-primary hover:border-theme-border'
193
+ )}
194
+ >
195
+ <LayoutGrid className="w-4 h-4" />
196
+ Catalog
197
+ </button>
198
+ </div>
199
+
200
+ {activeTab === 'releases' ? (
201
+ <>
202
+ {/* Releases Toolbar */}
203
+ <div className="flex items-center gap-4 px-4 py-3 border-b border-theme-border bg-theme-surface/50 shrink-0">
204
+ <div className="flex items-center gap-2 text-theme-text-secondary">
205
+ <Package className="w-5 h-5" />
206
+ <span className="font-medium">Helm Releases</span>
207
+ {!isFullyLoaded && (
208
+ <RefreshCw className="w-3.5 h-3.5 animate-spin text-theme-text-tertiary" />
209
+ )}
210
+ </div>
211
+ <div className="flex-1 relative">
212
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-theme-text-tertiary" />
213
+ <input
214
+ ref={searchInputRef}
215
+ type="text"
216
+ placeholder="Search releases..."
217
+ value={searchTerm}
218
+ onChange={(e) => setSearchTerm(e.target.value)}
219
+ className="w-full max-w-md pl-10 pr-4 py-2 bg-theme-elevated border border-theme-border-light rounded-lg text-sm text-theme-text-primary placeholder-theme-text-disabled focus:outline-none focus:ring-2 focus:ring-blue-500"
220
+ />
221
+ </div>
222
+ <button
223
+ onClick={handleRefresh}
224
+ disabled={isRefreshAnimating}
225
+ className="p-2 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded-lg disabled:opacity-50"
226
+ title="Refresh"
227
+ >
228
+ <RefreshCw className={clsx('w-4 h-4', isRefreshAnimating && 'animate-spin')} />
229
+ </button>
230
+ </div>
231
+
232
+ {/* Releases Table */}
233
+ <div className="flex-1 overflow-auto">
234
+ {isLoading ? (
235
+ <div className="flex items-center justify-center h-full text-theme-text-tertiary">
236
+ Loading...
237
+ </div>
238
+ ) : isForbidden ? (
239
+ <div className="flex flex-col items-center justify-center h-full text-theme-text-tertiary">
240
+ <Shield className="w-8 h-8 text-amber-400 mb-2" />
241
+ <p className="text-theme-text-secondary font-medium">Access Restricted</p>
242
+ <p className="text-sm mt-1">Insufficient permissions to list Helm releases</p>
243
+ </div>
244
+ ) : filteredReleases.length === 0 ? (
245
+ <div className="flex flex-col items-center justify-center h-full text-theme-text-tertiary gap-2">
246
+ <Package className="w-12 h-12 text-theme-text-disabled" />
247
+ <span>No Helm releases found</span>
248
+ {searchTerm && (
249
+ <button
250
+ onClick={() => setSearchTerm('')}
251
+ className="text-blue-400 hover:text-blue-300 text-sm"
252
+ >
253
+ Clear search
254
+ </button>
255
+ )}
256
+ {!searchTerm && (
257
+ <button
258
+ onClick={() => setActiveTab('charts')}
259
+ className="mt-2 px-4 py-2 text-sm text-skyhook-400 hover:text-skyhook-300 border border-skyhook-500/30 rounded-lg hover:bg-skyhook-500/10 transition-colors"
260
+ >
261
+ Browse charts to install
262
+ </button>
263
+ )}
264
+ </div>
265
+ ) : (
266
+ <table className="w-full table-fixed">
267
+ <thead className="bg-theme-surface sticky top-0 z-10">
268
+ <tr>
269
+ <th className="text-left px-4 py-3 text-xs font-medium text-theme-text-secondary uppercase tracking-wide">
270
+ Name
271
+ </th>
272
+ <th className="text-left px-4 py-3 text-xs font-medium text-theme-text-secondary uppercase tracking-wide w-32">
273
+ Namespace
274
+ </th>
275
+ <th className="text-left px-4 py-3 text-xs font-medium text-theme-text-secondary uppercase tracking-wide w-48">
276
+ Chart
277
+ </th>
278
+ <th className="text-left px-4 py-3 text-xs font-medium text-theme-text-secondary uppercase tracking-wide w-24 hidden xl:table-cell">
279
+ App Version
280
+ </th>
281
+ <th className="text-left px-4 py-3 text-xs font-medium text-theme-text-secondary uppercase tracking-wide w-28">
282
+ Status
283
+ </th>
284
+ <th className="text-left px-4 py-3 text-xs font-medium text-theme-text-secondary uppercase tracking-wide w-20">
285
+ Rev
286
+ </th>
287
+ <th className="text-left px-4 py-3 text-xs font-medium text-theme-text-secondary uppercase tracking-wide w-24">
288
+ Updated
289
+ </th>
290
+ </tr>
291
+ </thead>
292
+ <tbody className="table-divide-subtle">
293
+ {filteredReleases.map((release, index) => (
294
+ <ReleaseRow
295
+ key={`${release.namespace}-${release.name}`}
296
+ ref={index === highlightedIndex ? highlightedRowRef : null}
297
+ release={release}
298
+ upgradeInfo={upgradeInfo?.releases[`${release.namespace}/${release.name}`]}
299
+ isSelected={
300
+ selectedRelease?.namespace === release.namespace &&
301
+ selectedRelease?.name === release.name
302
+ }
303
+ isHighlighted={index === highlightedIndex}
304
+ onClick={() => onReleaseClick?.(release.namespace, release.name)}
305
+ onMouseEnter={() => setHighlightedIndex(-1)}
306
+ />
307
+ ))}
308
+ </tbody>
309
+ </table>
310
+ )}
311
+ </div>
312
+ </>
313
+ ) : (
314
+ <ChartBrowser onChartSelect={handleChartSelect} />
315
+ )}
316
+ </div>
317
+
318
+ {/* Install wizard modal */}
319
+ {selectedChart && (
320
+ <InstallWizard
321
+ repo={selectedChart.repo}
322
+ chartName={selectedChart.chart}
323
+ version={selectedChart.version}
324
+ source={selectedChart.source}
325
+ onClose={() => setSelectedChart(null)}
326
+ onSuccess={handleInstallSuccess}
327
+ />
328
+ )}
329
+ </div>
330
+ )
331
+ }
332
+
333
+ interface ReleaseRowProps {
334
+ release: HelmRelease
335
+ upgradeInfo?: UpgradeInfo
336
+ isSelected: boolean
337
+ isHighlighted?: boolean
338
+ onClick: () => void
339
+ onMouseEnter?: () => void
340
+ }
341
+
342
+ // Get actionable tooltip content for health issues
343
+ function getActionableTooltip(issue: string | undefined, summary: string | undefined, health: string): React.ReactNode {
344
+ const issueDetails: Record<string, { description: string; action: string }> = {
345
+ OOMKilled: {
346
+ description: 'Container exceeded its memory limit and was killed.',
347
+ action: 'Increase memory limits in Helm values or optimize app memory usage.',
348
+ },
349
+ CrashLoopBackOff: {
350
+ description: 'Container is repeatedly crashing.',
351
+ action: 'Check pod logs for crash reason.',
352
+ },
353
+ ImagePullBackOff: {
354
+ description: 'Cannot pull container image.',
355
+ action: 'Verify image name in Helm values and registry credentials.',
356
+ },
357
+ }
358
+
359
+ const details = issue ? issueDetails[issue] : null
360
+
361
+ return (
362
+ <div className="max-w-xs">
363
+ <div className={clsx(
364
+ 'font-medium',
365
+ health === 'unhealthy' ? 'text-red-400' : 'text-yellow-400'
366
+ )}>
367
+ {summary || issue || health}
368
+ </div>
369
+ {details && (
370
+ <>
371
+ <div className="text-theme-text-secondary text-[10px] mt-1">{details.description}</div>
372
+ <div className="text-blue-400 text-[10px] mt-1.5 border-t border-theme-border pt-1.5">
373
+ 💡 {details.action}
374
+ </div>
375
+ </>
376
+ )}
377
+ {!details && issue && (
378
+ <div className="text-blue-400 text-[10px] mt-1.5">Click release for details</div>
379
+ )}
380
+ </div>
381
+ )
382
+ }
383
+
384
+ const ReleaseRow = forwardRef<HTMLTableRowElement, ReleaseRowProps>(
385
+ function ReleaseRow({ release, upgradeInfo, isSelected, isHighlighted, onClick, onMouseEnter }, ref) {
386
+ // Health badge styling
387
+ const getHealthBadge = () => {
388
+ if (!release.resourceHealth || release.resourceHealth === 'unknown') return null
389
+
390
+ const healthStyles: Record<string, { bg: string; text: string; dot: string }> = {
391
+ healthy: { bg: 'bg-green-500/10', text: 'text-green-400', dot: 'bg-green-500' },
392
+ degraded: { bg: 'bg-yellow-500/10', text: 'text-yellow-400', dot: 'bg-yellow-500' },
393
+ unhealthy: { bg: 'bg-red-500/10', text: 'text-red-400', dot: 'bg-red-500' },
394
+ }
395
+
396
+ const style = healthStyles[release.resourceHealth] || healthStyles.healthy
397
+ const tooltipContent = getActionableTooltip(release.healthIssue, release.healthSummary, release.resourceHealth)
398
+
399
+ return (
400
+ <Tooltip content={tooltipContent}>
401
+ <span className={clsx(
402
+ 'flex items-center gap-1 px-1.5 py-0.5 text-xs font-medium rounded shrink-0',
403
+ style.bg, style.text
404
+ )}>
405
+ <span className={clsx('w-1.5 h-1.5 rounded-full', style.dot)} />
406
+ {release.healthIssue || (release.resourceHealth !== 'healthy' ? release.healthSummary : null)}
407
+ </span>
408
+ </Tooltip>
409
+ )
410
+ }
411
+
412
+ return (
413
+ <tr
414
+ ref={ref}
415
+ onClick={onClick}
416
+ onMouseEnter={onMouseEnter}
417
+ className={clsx(
418
+ 'cursor-pointer transition-colors',
419
+ isSelected
420
+ ? 'selection-strong hover:bg-skyhook-500/30'
421
+ : isHighlighted
422
+ ? 'selection selection-ring'
423
+ : 'hover:bg-theme-surface/50'
424
+ )}
425
+ >
426
+ <td className="px-4 py-3">
427
+ <div className="flex items-center gap-2">
428
+ <Package className="w-4 h-4 text-theme-text-tertiary shrink-0" />
429
+ <span className="text-sm text-theme-text-primary font-medium truncate">{release.name}</span>
430
+ {getHealthBadge()}
431
+ {upgradeInfo?.updateAvailable && (
432
+ <Tooltip content={`Upgrade available: ${release.chartVersion} → ${upgradeInfo.latestVersion}`}>
433
+ <span className={clsx('badge-sm shrink-0', SEVERITY_BADGE.warning)}>
434
+ <ArrowUpCircle className="w-3 h-3" />
435
+ </span>
436
+ </Tooltip>
437
+ )}
438
+ </div>
439
+ </td>
440
+ <td className="px-4 py-3 w-32">
441
+ <span className="text-sm text-theme-text-secondary">{release.namespace}</span>
442
+ </td>
443
+ <td className="px-4 py-3 w-48">
444
+ <Tooltip content={`${release.chart}-${release.chartVersion}`}>
445
+ <span className="text-sm text-theme-text-secondary truncate block">
446
+ {truncate(`${release.chart}-${release.chartVersion}`, 35)}
447
+ </span>
448
+ </Tooltip>
449
+ </td>
450
+ <td className="px-4 py-3 w-24 hidden xl:table-cell">
451
+ <span className="text-sm text-theme-text-secondary">{release.appVersion || '-'}</span>
452
+ </td>
453
+ <td className="px-4 py-3 w-28">
454
+ <span
455
+ className={clsx(
456
+ 'badge',
457
+ getStatusColor(release.status)
458
+ )}
459
+ >
460
+ {release.status}
461
+ </span>
462
+ </td>
463
+ <td className="px-4 py-3 w-20">
464
+ <span className="text-sm text-theme-text-secondary">{release.revision}</span>
465
+ </td>
466
+ <td className="px-4 py-3 w-24">
467
+ <Tooltip content={release.updated}>
468
+ <span className="text-sm text-theme-text-secondary">
469
+ {formatAge(release.updated)}
470
+ </span>
471
+ </Tooltip>
472
+ </td>
473
+ </tr>
474
+ )
475
+ })