@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,112 @@
1
+ import type { OpenCostSummary } from '../../api/client'
2
+ import { useOpenCostSummary } from '../../api/client'
3
+ import { DollarSign } from 'lucide-react'
4
+
5
+ export function CostCard({ onNavigate }: { onNavigate?: () => void }) {
6
+ const { data } = useOpenCostSummary()
7
+
8
+ // Only show when OpenCost data is actually available — no placeholder card
9
+ if (!data || !data.available) {
10
+ return null
11
+ }
12
+
13
+ return <CostCardContent data={data} onNavigate={onNavigate} />
14
+ }
15
+
16
+ function CostCardContent({ data, onNavigate }: { data: OpenCostSummary; onNavigate?: () => void }) {
17
+ const hourlyCost = data.totalHourlyCost ?? 0
18
+ const monthlyCost = hourlyCost * 730
19
+ const namespaces = data.namespaces ?? []
20
+ const topNamespaces = namespaces.slice(0, 5)
21
+
22
+ // Find the max cost for bar scaling
23
+ const maxCost = topNamespaces.length > 0 ? topNamespaces[0].hourlyCost : 0
24
+
25
+ return (
26
+ <div
27
+ onClick={onNavigate}
28
+ className={`h-[260px] rounded-xl bg-theme-surface shadow-theme-sm text-left animate-fade-in-up ${onNavigate ? 'cursor-pointer hover:-translate-y-1 hover:shadow-theme-md transition-all duration-200' : ''}`}
29
+ >
30
+ <div className="flex flex-col h-full w-full">
31
+ <div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50">
32
+ <div className="flex items-center gap-2">
33
+ <DollarSign className="w-4 h-4 text-indigo-500" />
34
+ <span className="text-xs font-semibold uppercase tracking-wider text-indigo-500">Cost Insights</span>
35
+ {namespaces.length > 0 && (
36
+ <span className="badge-sm bg-indigo-100 text-indigo-700 border-indigo-300 dark:bg-indigo-950/50 dark:text-indigo-400 dark:border-indigo-700/40">
37
+ {namespaces.length} ns
38
+ </span>
39
+ )}
40
+ </div>
41
+ </div>
42
+
43
+ <div className="flex-1 min-h-0 flex flex-col px-4 py-3">
44
+ {/* Hero cost numbers */}
45
+ <div className="flex items-baseline gap-3 mb-3">
46
+ <div className="flex items-baseline gap-1">
47
+ <span className="text-2xl font-bold text-theme-text-primary tabular-nums">
48
+ {formatCost(hourlyCost)}
49
+ </span>
50
+ <span className="text-xs text-theme-text-tertiary">/hr</span>
51
+ </div>
52
+ <div className="flex items-baseline gap-1 text-theme-text-secondary">
53
+ <span className="text-sm font-medium tabular-nums">~{formatCost(monthlyCost)}</span>
54
+ <span className="text-[10px] text-theme-text-tertiary">/mo</span>
55
+ </div>
56
+ </div>
57
+
58
+ {/* Top namespaces */}
59
+ <div className="flex-1 min-h-0 space-y-1.5">
60
+ {topNamespaces.map((ns) => {
61
+ const pct = maxCost > 0 ? (ns.hourlyCost / maxCost) * 100 : 0
62
+ return (
63
+ <div key={ns.name} className="flex items-center gap-2">
64
+ <span className="text-[11px] text-theme-text-secondary truncate w-24 shrink-0">{ns.name}</span>
65
+ <div className="flex-1 h-2 rounded-full overflow-hidden bg-theme-hover">
66
+ <div
67
+ className="h-full rounded-full bg-indigo-500/60"
68
+ style={{ width: `${Math.max(pct, 2)}%` }}
69
+ />
70
+ </div>
71
+ <span className="text-[10px] text-theme-text-tertiary tabular-nums w-14 text-right shrink-0">
72
+ {formatCost(ns.hourlyCost)}/h
73
+ </span>
74
+ </div>
75
+ )
76
+ })}
77
+ {namespaces.length > 5 && (
78
+ <span className="text-[10px] text-theme-text-tertiary">
79
+ +{namespaces.length - 5} more namespaces
80
+ </span>
81
+ )}
82
+ </div>
83
+ </div>
84
+
85
+ <div className="px-4 py-1.5 border-t border-theme-border/50 flex items-center justify-between">
86
+ <span className="text-[10px] text-theme-text-tertiary">
87
+ {data.currency ?? 'USD'} &middot; {data.window ?? '1h'} window
88
+ </span>
89
+ <span className="flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider text-indigo-500">
90
+ OpenCost
91
+ </span>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ )
96
+ }
97
+
98
+ function formatCost(value: number): string {
99
+ if (value >= 1000) {
100
+ return `$${(value / 1000).toFixed(1)}k`
101
+ }
102
+ if (value >= 1) {
103
+ return `$${value.toFixed(2)}`
104
+ }
105
+ if (value >= 0.01) {
106
+ return `$${value.toFixed(3)}`
107
+ }
108
+ if (value > 0) {
109
+ return `$${value.toFixed(4)}`
110
+ }
111
+ return '$0.00'
112
+ }
@@ -0,0 +1 @@
1
+ export { HealthRing } from '@skyhook-io/k8s-ui/components/ui/HealthRing'
@@ -0,0 +1,129 @@
1
+ import type { DashboardHelmSummary } from '../../api/client'
2
+ import { Package, ArrowRight, Shield } from 'lucide-react'
3
+ import { clsx } from 'clsx'
4
+ import { Tooltip } from '../ui/Tooltip'
5
+
6
+ interface HelmSummaryProps {
7
+ data?: DashboardHelmSummary
8
+ onNavigate: () => void
9
+ }
10
+
11
+ function getStatusBadgeClass(status: string): string {
12
+ switch (status.toLowerCase()) {
13
+ case 'deployed':
14
+ case 'succeeded':
15
+ return 'status-healthy'
16
+ case 'failed':
17
+ return 'status-unhealthy'
18
+ case 'pending-install':
19
+ case 'pending-upgrade':
20
+ case 'pending-rollback':
21
+ return 'status-degraded'
22
+ case 'superseded':
23
+ case 'uninstalled':
24
+ return 'status-unknown'
25
+ default:
26
+ return 'status-unknown'
27
+ }
28
+ }
29
+
30
+ function getHealthDot(health?: string): string {
31
+ switch (health) {
32
+ case 'healthy':
33
+ return 'bg-green-500'
34
+ case 'degraded':
35
+ return 'bg-yellow-500'
36
+ case 'unhealthy':
37
+ return 'bg-red-500'
38
+ default:
39
+ return 'bg-theme-text-tertiary'
40
+ }
41
+ }
42
+
43
+ export function HelmSummary({ data, onNavigate }: HelmSummaryProps) {
44
+ return (
45
+ <button
46
+ onClick={onNavigate}
47
+ className="group h-[260px] rounded-xl bg-theme-surface shadow-theme-sm hover:-translate-y-1 hover:shadow-theme-md transition-all duration-200 text-left"
48
+ >
49
+ <div className="flex flex-col h-full w-full">
50
+ <div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50">
51
+ <div className="flex items-center gap-2">
52
+ <Package className="w-4 h-4 text-theme-text-tertiary" />
53
+ <span className="text-xs font-semibold uppercase tracking-wider text-theme-text-secondary">Helm Releases</span>
54
+ {data && data.total > 0 && (
55
+ <span className="badge-sm bg-theme-elevated text-theme-text-secondary">
56
+ {data.total}
57
+ </span>
58
+ )}
59
+ </div>
60
+ </div>
61
+
62
+ <div className="flex-1 min-h-0 overflow-hidden">
63
+ {!data ? (
64
+ <div className="divide-y divide-theme-border">
65
+ {[...Array(3)].map((_, i) => (
66
+ <div key={i} className="flex items-center justify-between px-3 py-1.5">
67
+ <div className="flex items-center gap-2 min-w-0">
68
+ <span className="w-1.5 h-1.5 rounded-full shrink-0 bg-theme-text-tertiary/30 animate-pulse" />
69
+ <span className="h-3 w-24 rounded bg-theme-text-tertiary/20 animate-pulse" />
70
+ <span className="h-3 w-14 rounded bg-theme-text-tertiary/10 animate-pulse" />
71
+ </div>
72
+ <div className="flex items-center gap-1.5 ml-2">
73
+ <span className="h-3 w-20 rounded bg-theme-text-tertiary/10 animate-pulse hidden sm:inline-block" />
74
+ <span className="h-4 w-14 rounded bg-theme-text-tertiary/15 animate-pulse" />
75
+ </div>
76
+ </div>
77
+ ))}
78
+ </div>
79
+ ) : data.restricted ? (
80
+ <div className="flex flex-col items-center justify-center h-full py-4 text-theme-text-tertiary">
81
+ <Shield className="w-8 h-8 text-amber-400 mb-2" />
82
+ <span className="text-xs font-medium text-theme-text-secondary">Access Restricted</span>
83
+ <span className="text-[11px] mt-1">Insufficient permissions to list Helm releases</span>
84
+ </div>
85
+ ) : !data.releases || data.releases.length === 0 ? (
86
+ <div className="flex items-center justify-center h-full py-4 text-xs text-theme-text-tertiary">
87
+ No Helm releases found
88
+ </div>
89
+ ) : (
90
+ <div className="divide-y divide-theme-border">
91
+ {data.releases.map((release) => (
92
+ <div
93
+ key={`${release.namespace}/${release.name}`}
94
+ className="flex items-center justify-between px-3 py-1.5 overflow-hidden"
95
+ >
96
+ <div className="flex items-center gap-2 min-w-0">
97
+ <span className={clsx('w-1.5 h-1.5 rounded-full shrink-0', getHealthDot(release.resourceHealth))} />
98
+ <span className="text-xs text-theme-text-primary truncate">{release.name}</span>
99
+ <span className="text-[10px] text-theme-text-tertiary truncate">{release.namespace}</span>
100
+ </div>
101
+ <div className="flex items-center gap-1.5 ml-2 min-w-0">
102
+ <Tooltip content={`${release.chart} ${release.chartVersion}`} delay={100}>
103
+ <span className="text-[10px] text-theme-text-tertiary hidden sm:inline truncate max-w-[150px]">
104
+ {release.chart} {release.chartVersion}
105
+ </span>
106
+ </Tooltip>
107
+ <span className={clsx('badge-sm shrink-0', getStatusBadgeClass(release.status))}>
108
+ {release.status}
109
+ </span>
110
+ </div>
111
+ </div>
112
+ ))}
113
+ </div>
114
+ )}
115
+ </div>
116
+
117
+ <div className="px-4 py-1.5 border-t border-theme-border/50 flex items-center justify-between">
118
+ <span className="text-[10px] text-theme-text-tertiary">
119
+ {data && data.releases && data.total > data.releases.length ? `+${data.total - data.releases.length} more` : ''}
120
+ </span>
121
+ <span className="flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider text-theme-text-secondary group-hover:text-theme-text-primary transition-colors">
122
+ Open Helm
123
+ <ArrowRight className="w-3.5 h-3.5 transition-transform group-hover:translate-x-0.5" />
124
+ </span>
125
+ </div>
126
+ </div>
127
+ </button>
128
+ )
129
+ }
@@ -0,0 +1,224 @@
1
+ import { useDashboard, useDashboardCRDs, useDashboardHelm } from '../../api/client'
2
+ import type { DashboardResponse } from '../../api/client'
3
+ import type { ExtendedMainView, Topology, SelectedResource } from '../../types'
4
+ import { kindToPlural } from '../../utils/navigation'
5
+ import { TopologyPreview } from './TopologyPreview'
6
+ import { HelmSummary } from './HelmSummary'
7
+ import { ActivitySummary } from './ActivitySummary'
8
+ import { TrafficSummary } from './TrafficSummary'
9
+ import { CertificateHealthCard } from './CertificateHealthCard'
10
+ import { NetworkPolicyCoverageCard } from './NetworkPolicyCoverageCard'
11
+ import { CostCard } from './CostCard'
12
+ import { AuditCard } from '@skyhook-io/k8s-ui'
13
+ import { ClusterHealthCard } from './ClusterHealthCard'
14
+ import { AlertTriangle, Loader2, Shield } from 'lucide-react'
15
+ import { clsx } from 'clsx'
16
+
17
+ interface HomeViewProps {
18
+ namespaces: string[]
19
+ topology: Topology | null
20
+ onNavigateToView: (view: ExtendedMainView, params?: Record<string, string>) => void
21
+ onNavigateToResourceKind: (kind: string, group?: string, filters?: Record<string, string[]>) => void
22
+ onNavigateToResource: (resource: SelectedResource) => void
23
+ }
24
+
25
+ export function HomeView({ namespaces, topology, onNavigateToView, onNavigateToResourceKind, onNavigateToResource }: HomeViewProps) {
26
+ const { data, isLoading, error } = useDashboard(namespaces)
27
+ // CRDs and Helm load lazily after main dashboard to keep initial load fast
28
+ const { data: crdsData } = useDashboardCRDs(namespaces)
29
+ const { data: helmData } = useDashboardHelm(namespaces)
30
+
31
+ if (isLoading) {
32
+ return (
33
+ <div className="flex-1 flex items-center justify-center">
34
+ <div className="flex flex-col items-center gap-3">
35
+ <Loader2 className="w-6 h-6 animate-spin text-theme-text-tertiary" />
36
+ <span className="text-sm text-theme-text-tertiary">Loading dashboard...</span>
37
+ </div>
38
+ </div>
39
+ )
40
+ }
41
+
42
+ if (error || !data) {
43
+ return (
44
+ <div className="flex-1 flex items-center justify-center text-theme-text-secondary">
45
+ <p>Failed to load dashboard data</p>
46
+ </div>
47
+ )
48
+ }
49
+
50
+ if (data.accessRestricted) {
51
+ return (
52
+ <div className="flex-1 flex items-center justify-center bg-theme-base">
53
+ <div className="flex flex-col items-center gap-3 max-w-md text-center">
54
+ <div className="w-12 h-12 rounded-full bg-amber-500/10 flex items-center justify-center">
55
+ <Shield className="w-6 h-6 text-amber-500" />
56
+ </div>
57
+ <p className="text-lg font-medium text-theme-text-primary">No Namespace Access</p>
58
+ <p className="text-sm text-theme-text-secondary">
59
+ Your account does not have access to any namespaces in this cluster. Contact your administrator to add a Kubernetes RoleBinding or ClusterRoleBinding for your user.
60
+ </p>
61
+ </div>
62
+ </div>
63
+ )
64
+ }
65
+
66
+ const hasProblems = data.problems && data.problems.length > 0
67
+
68
+ return (
69
+ <div className="flex-1 overflow-y-auto">
70
+ <div className="max-w-[1600px] mx-auto px-6 py-6 space-y-6">
71
+ {/* Row 1: Cluster Health Card (combined health + resource counts) */}
72
+ <ClusterHealthCard
73
+ health={data.health}
74
+ counts={data.resourceCounts}
75
+ cluster={data.cluster}
76
+ metrics={data.metrics}
77
+ metricsServerAvailable={data.metricsServerAvailable}
78
+ topCRDs={crdsData?.topCRDs}
79
+ problems={data.problems ?? []}
80
+ nodeVersionSkew={data.nodeVersionSkew}
81
+ onNavigateToKind={onNavigateToResourceKind}
82
+ onNavigateToView={() => onNavigateToView('resources')}
83
+ onWarningEventsClick={() => onNavigateToView('timeline', { view: 'list', filter: 'warnings', time: 'all' })}
84
+ onUnhealthyClick={() => onNavigateToView('timeline', { view: 'list', filter: 'unhealthy', time: 'all' })}
85
+ />
86
+
87
+ {/* Row 2: Main content columns — teasers left, problems right (if any) */}
88
+ <div className={clsx(
89
+ 'grid gap-6',
90
+ hasProblems ? 'grid-cols-1 lg:grid-cols-[1fr_420px]' : 'grid-cols-1'
91
+ )}>
92
+ {/* Left column: teaser cards */}
93
+ <div className="flex flex-col gap-6 auto-rows-min">
94
+ {/* Primary cards — 2-col grid */}
95
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
96
+ <TopologyPreview
97
+ topology={topology}
98
+ summary={data.topologySummary}
99
+ onNavigate={() => onNavigateToView('topology')}
100
+ />
101
+ <HelmSummary
102
+ data={helmData}
103
+ onNavigate={() => onNavigateToView('helm')}
104
+ />
105
+ <ActivitySummary
106
+ namespaces={namespaces}
107
+ topology={topology}
108
+ onNavigate={() => onNavigateToView('timeline')}
109
+ />
110
+ <TrafficSummary
111
+ data={data.trafficSummary}
112
+ onNavigate={() => onNavigateToView('traffic')}
113
+ />
114
+ <CostCard onNavigate={() => onNavigateToView('cost')} />
115
+ </div>
116
+
117
+ {/* Health & compliance cards — 3-col when enough cards, 2-col fallback */}
118
+ {(data.certificateHealth || data.networkPolicyCoverage || data.audit) && (() => {
119
+ const healthCards = [
120
+ data.certificateHealth && (
121
+ <CertificateHealthCard
122
+ key="certs"
123
+ data={data.certificateHealth}
124
+ onNavigate={() => onNavigateToResourceKind('secrets', undefined, { type: ['TLS'] })}
125
+ />
126
+ ),
127
+ data.networkPolicyCoverage && (
128
+ <NetworkPolicyCoverageCard
129
+ key="netpol"
130
+ data={data.networkPolicyCoverage}
131
+ onNavigate={() => onNavigateToResourceKind('networkpolicies', 'networking.k8s.io')}
132
+ />
133
+ ),
134
+ data.audit && (
135
+ <AuditCard
136
+ key="audit"
137
+ data={data.audit}
138
+ onNavigate={() => onNavigateToView('audit')}
139
+ />
140
+ ),
141
+ ].filter(Boolean)
142
+
143
+ return (
144
+ <div className={clsx(
145
+ 'grid gap-6',
146
+ healthCards.length >= 3 ? 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3' : 'grid-cols-1 sm:grid-cols-2'
147
+ )}>
148
+ {healthCards}
149
+ </div>
150
+ )
151
+ })()}
152
+ </div>
153
+
154
+ {/* Right column: problems panel */}
155
+ {hasProblems && (
156
+ <ProblemsPanel
157
+ problems={data.problems}
158
+ onResourceClick={onNavigateToResource}
159
+ />
160
+ )}
161
+ </div>
162
+ </div>
163
+ </div>
164
+ )
165
+ }
166
+
167
+ // ============================================================================
168
+ // Problems Panel (right sidebar, scrollable)
169
+ // ============================================================================
170
+
171
+ interface ProblemsPanelProps {
172
+ problems: DashboardResponse['problems']
173
+ onResourceClick: (resource: SelectedResource) => void
174
+ }
175
+
176
+
177
+ function ProblemsPanel({ problems, onResourceClick }: ProblemsPanelProps) {
178
+ return (
179
+ <div className="rounded-xl bg-theme-surface shadow-theme-sm flex flex-col lg:max-h-[calc(100vh-280px)] lg:sticky lg:top-0">
180
+ <div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50 shrink-0">
181
+ <div className="flex items-center gap-2">
182
+ <AlertTriangle className="w-4 h-4 text-red-500" />
183
+ <span className="text-xs font-semibold uppercase tracking-wider text-red-500">Unhealthy Workloads</span>
184
+ </div>
185
+ <span className="badge status-unhealthy rounded-full">{problems.length}</span>
186
+ </div>
187
+ <div className="overflow-y-auto flex-1 min-h-0">
188
+ <div className="divide-y divide-theme-border">
189
+ {problems.map((p, i) => (
190
+ <button
191
+ key={`${p.kind}-${p.namespace}-${p.name}-${i}`}
192
+ className="w-full flex items-center gap-2 px-3 py-1.5 hover:bg-theme-hover transition-colors text-left"
193
+ onClick={() => onResourceClick({
194
+ kind: kindToPlural(p.kind),
195
+ namespace: p.namespace,
196
+ name: p.name,
197
+ group: p.group,
198
+ })}
199
+ >
200
+ <span className={clsx(
201
+ 'w-1.5 h-1.5 rounded-full shrink-0',
202
+ p.severity === 'critical' ? 'bg-red-400' : p.severity === 'high' ? 'bg-orange-400' : 'bg-yellow-400'
203
+ )} />
204
+ <div className="min-w-0 flex-1">
205
+ <div className="flex items-center gap-1.5">
206
+ <span className="text-[10px] text-theme-text-tertiary bg-theme-elevated px-1 py-0.5 rounded">{p.kind}</span>
207
+ <span className="text-xs text-theme-text-primary truncate font-medium">{p.name}</span>
208
+ <span className="text-[10px] text-theme-text-tertiary ml-auto shrink-0">{p.duration || p.age}</span>
209
+ </div>
210
+ <div className="flex items-center gap-1.5 mt-0.5">
211
+ <span className="text-[11px] text-theme-text-secondary truncate">{p.reason}</span>
212
+ <span className="text-[10px] text-theme-text-tertiary shrink-0">{p.namespace}</span>
213
+ </div>
214
+ {p.message && (
215
+ <div className="text-[10px] text-theme-text-tertiary truncate mt-0.5">{p.message}</div>
216
+ )}
217
+ </div>
218
+ </button>
219
+ ))}
220
+ </div>
221
+ </div>
222
+ </div>
223
+ )
224
+ }