@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,365 @@
1
+ import { useState, useCallback } from 'react'
2
+ import { Copy, Check, Settings, Pencil, X, Eye, Play, Loader2 } from 'lucide-react'
3
+ import { clsx } from 'clsx'
4
+ import yaml from 'yaml'
5
+ import type { HelmValues, ValuesPreviewResponse } from '../../types'
6
+ import { CodeViewer } from '../ui/CodeViewer'
7
+ import { YamlEditor } from '../ui/YamlEditor'
8
+ import { useHelmPreviewValues, useHelmApplyValues } from '../../api/client'
9
+ import { useCanHelmWrite } from '../../contexts/CapabilitiesContext'
10
+ import { ValuesDiffPreview } from './ValuesDiffPreview'
11
+
12
+ interface ValuesViewerProps {
13
+ values?: HelmValues
14
+ isLoading: boolean
15
+ showAllValues: boolean
16
+ onToggleAllValues: (show: boolean) => void
17
+ onCopy: (text: string) => void
18
+ copied: boolean
19
+ // Required for editing
20
+ namespace?: string
21
+ name?: string
22
+ onApplySuccess?: () => void
23
+ }
24
+
25
+ export function ValuesViewer({
26
+ values,
27
+ isLoading,
28
+ showAllValues,
29
+ onToggleAllValues,
30
+ onCopy,
31
+ copied,
32
+ namespace,
33
+ name,
34
+ onApplySuccess,
35
+ }: ValuesViewerProps) {
36
+ const [isEditing, setIsEditing] = useState(false)
37
+ const [editedYaml, setEditedYaml] = useState('')
38
+ const [yamlError, setYamlError] = useState<string | null>(null)
39
+ const [previewData, setPreviewData] = useState<ValuesPreviewResponse | null>(null)
40
+ const [showPreview, setShowPreview] = useState(false)
41
+
42
+ const previewMutation = useHelmPreviewValues()
43
+ const applyMutation = useHelmApplyValues()
44
+ const canHelmWrite = useCanHelmWrite()
45
+
46
+ const canEdit = Boolean(namespace && name) && canHelmWrite
47
+
48
+ const displayValues = showAllValues && values?.computed ? values.computed : values?.userSupplied
49
+ const isEmpty = !displayValues || Object.keys(displayValues).length === 0
50
+
51
+ // Start editing mode
52
+ const handleStartEdit = useCallback(() => {
53
+ // Allow editing even with no user-supplied values (start with empty YAML)
54
+ const yamlStr = values?.userSupplied ? jsonToYaml(values.userSupplied) : ''
55
+ setEditedYaml(yamlStr)
56
+ setYamlError(null)
57
+ setIsEditing(true)
58
+ // Switch to user-supplied view when editing
59
+ if (showAllValues) {
60
+ onToggleAllValues(false)
61
+ }
62
+ }, [values?.userSupplied, showAllValues, onToggleAllValues])
63
+
64
+ // Cancel editing
65
+ const handleCancelEdit = useCallback(() => {
66
+ setIsEditing(false)
67
+ setEditedYaml('')
68
+ setYamlError(null)
69
+ setPreviewData(null)
70
+ setShowPreview(false)
71
+ }, [])
72
+
73
+ // Parse YAML and validate
74
+ const parseYaml = useCallback((yamlStr: string): Record<string, unknown> | null => {
75
+ try {
76
+ const parsed = yaml.parse(yamlStr)
77
+ setYamlError(null)
78
+ return parsed || {}
79
+ } catch (err) {
80
+ setYamlError(err instanceof Error ? err.message : 'Invalid YAML')
81
+ return null
82
+ }
83
+ }, [])
84
+
85
+ // Preview changes
86
+ const handlePreview = useCallback(async () => {
87
+ if (!namespace || !name) return
88
+ const parsed = parseYaml(editedYaml)
89
+ if (!parsed) return
90
+
91
+ try {
92
+ const result = await previewMutation.mutateAsync({
93
+ namespace,
94
+ name,
95
+ values: parsed,
96
+ })
97
+ setPreviewData(result)
98
+ setShowPreview(true)
99
+ } catch {
100
+ // Error is handled by mutation
101
+ }
102
+ }, [namespace, name, editedYaml, parseYaml, previewMutation])
103
+
104
+ // Apply changes
105
+ const handleApply = useCallback(async () => {
106
+ if (!namespace || !name) return
107
+ const parsed = parseYaml(editedYaml)
108
+ if (!parsed) return
109
+
110
+ try {
111
+ await applyMutation.mutateAsync({
112
+ namespace,
113
+ name,
114
+ values: parsed,
115
+ })
116
+ handleCancelEdit()
117
+ onApplySuccess?.()
118
+ } catch {
119
+ // Error is handled by mutation
120
+ }
121
+ }, [namespace, name, editedYaml, parseYaml, applyMutation, handleCancelEdit, onApplySuccess])
122
+
123
+ // Apply from preview modal
124
+ const handleApplyFromPreview = useCallback(async () => {
125
+ if (!previewData || !namespace || !name) return
126
+ try {
127
+ await applyMutation.mutateAsync({
128
+ namespace,
129
+ name,
130
+ values: previewData.newValues,
131
+ })
132
+ setShowPreview(false)
133
+ handleCancelEdit()
134
+ onApplySuccess?.()
135
+ } catch {
136
+ // Error is handled by mutation
137
+ }
138
+ }, [previewData, namespace, name, applyMutation, handleCancelEdit, onApplySuccess])
139
+
140
+ if (isLoading) {
141
+ return (
142
+ <div className="flex items-center justify-center h-32 text-theme-text-tertiary">
143
+ Loading values...
144
+ </div>
145
+ )
146
+ }
147
+
148
+ if (isEmpty && !isEditing) {
149
+ return (
150
+ <div className="p-4">
151
+ <div className="flex items-center justify-between mb-3">
152
+ <span className="text-sm font-medium text-theme-text-secondary">Values</span>
153
+ <div className="flex items-center gap-2">
154
+ <ToggleButton showAll={showAllValues} onToggle={onToggleAllValues} disabled={isEditing} />
155
+ {canEdit && (
156
+ <button
157
+ onClick={handleStartEdit}
158
+ className="flex items-center gap-1 px-2 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
159
+ >
160
+ <Pencil className="w-3.5 h-3.5" />
161
+ Edit
162
+ </button>
163
+ )}
164
+ </div>
165
+ </div>
166
+ <div className="flex flex-col items-center justify-center h-32 text-theme-text-tertiary gap-2">
167
+ <Settings className="w-8 h-8 text-theme-text-disabled" />
168
+ <span>{showAllValues ? 'No computed values' : 'No user-supplied values'}</span>
169
+ </div>
170
+ </div>
171
+ )
172
+ }
173
+
174
+ const yamlContent = isEditing ? editedYaml : jsonToYaml(displayValues || {})
175
+
176
+ return (
177
+ <div className="p-4 flex flex-col h-full">
178
+ {/* Header */}
179
+ <div className="flex items-center justify-between mb-3">
180
+ <div className="flex items-center gap-2">
181
+ <span className="text-sm font-medium text-theme-text-secondary">
182
+ {isEditing ? 'Editing Values' : showAllValues ? 'All Values (Computed)' : 'User-Supplied Values'}
183
+ </span>
184
+ {isEditing && (
185
+ <span className="badge-sm bg-amber-500/20 text-amber-400 border-amber-500/30">
186
+ unsaved
187
+ </span>
188
+ )}
189
+ </div>
190
+ <div className="flex items-center gap-2">
191
+ {!isEditing && (
192
+ <>
193
+ <ToggleButton showAll={showAllValues} onToggle={onToggleAllValues} disabled={isEditing} />
194
+ <button
195
+ onClick={() => onCopy(yamlContent)}
196
+ className="flex items-center gap-1 px-2 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
197
+ >
198
+ {copied ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
199
+ Copy
200
+ </button>
201
+ {canEdit && (
202
+ <button
203
+ onClick={handleStartEdit}
204
+ className="flex items-center gap-1 px-2 py-1 text-xs text-blue-400 hover:text-blue-300 hover:bg-blue-500/10 rounded border border-blue-500/30"
205
+ >
206
+ <Pencil className="w-3.5 h-3.5" />
207
+ Edit
208
+ </button>
209
+ )}
210
+ </>
211
+ )}
212
+ {isEditing && (
213
+ <>
214
+ <button
215
+ onClick={handleCancelEdit}
216
+ className="flex items-center gap-1 px-2 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
217
+ >
218
+ <X className="w-3.5 h-3.5" />
219
+ Cancel
220
+ </button>
221
+ <button
222
+ onClick={handlePreview}
223
+ disabled={!!yamlError || previewMutation.isPending}
224
+ className="flex items-center gap-1 px-2 py-1 text-xs text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded border border-theme-border disabled:opacity-50 disabled:cursor-not-allowed"
225
+ >
226
+ {previewMutation.isPending ? (
227
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
228
+ ) : (
229
+ <Eye className="w-3.5 h-3.5" />
230
+ )}
231
+ Preview
232
+ </button>
233
+ <button
234
+ onClick={handleApply}
235
+ disabled={!!yamlError || applyMutation.isPending || !canHelmWrite}
236
+ className="flex items-center gap-1 px-2.5 py-1 text-xs btn-brand rounded disabled:cursor-not-allowed"
237
+ title={!canHelmWrite ? 'Helm write permissions required (rbac.helm=true)' : undefined}
238
+ >
239
+ {applyMutation.isPending ? (
240
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
241
+ ) : (
242
+ <Play className="w-3.5 h-3.5" />
243
+ )}
244
+ Apply
245
+ </button>
246
+ </>
247
+ )}
248
+ </div>
249
+ </div>
250
+
251
+ {/* Error message */}
252
+ {yamlError && (
253
+ <div className="mb-3 px-3 py-2 text-xs text-red-400 bg-red-500/10 border border-red-500/30 rounded">
254
+ {yamlError}
255
+ </div>
256
+ )}
257
+
258
+ {/* Mutation error */}
259
+ {(previewMutation.error || applyMutation.error) && (
260
+ <div className="mb-3 px-3 py-2 text-xs text-red-400 bg-red-500/10 border border-red-500/30 rounded">
261
+ {previewMutation.error?.message || applyMutation.error?.message}
262
+ </div>
263
+ )}
264
+
265
+ {/* Editor / Viewer */}
266
+ <div className="flex-1 min-h-0">
267
+ {isEditing ? (
268
+ <YamlEditor
269
+ value={editedYaml}
270
+ onChange={setEditedYaml}
271
+ height="calc(100vh - 400px)"
272
+ onValidate={(isValid, errors) => {
273
+ setYamlError(isValid ? null : errors[0] || 'Invalid YAML')
274
+ }}
275
+ />
276
+ ) : (
277
+ <CodeViewer
278
+ code={yamlContent}
279
+ language="yaml"
280
+ showLineNumbers
281
+ maxHeight="calc(100vh - 300px)"
282
+ />
283
+ )}
284
+ </div>
285
+
286
+ {/* Preview Modal */}
287
+ {showPreview && previewData && (
288
+ <ValuesDiffPreview
289
+ previewData={previewData}
290
+ onClose={() => setShowPreview(false)}
291
+ onApply={handleApplyFromPreview}
292
+ isApplying={applyMutation.isPending}
293
+ />
294
+ )}
295
+ </div>
296
+ )
297
+ }
298
+
299
+ function ToggleButton({ showAll, onToggle, disabled }: { showAll: boolean; onToggle: (show: boolean) => void; disabled?: boolean }) {
300
+ return (
301
+ <div className={clsx('flex items-center bg-theme-elevated/50 rounded-md p-0.5 text-xs', disabled && 'opacity-50 pointer-events-none')}>
302
+ <button
303
+ onClick={() => onToggle(false)}
304
+ disabled={disabled}
305
+ className={clsx(
306
+ 'px-2 py-1 rounded transition-colors',
307
+ !showAll ? 'bg-theme-hover text-theme-text-primary' : 'text-theme-text-secondary hover:text-theme-text-primary'
308
+ )}
309
+ >
310
+ User
311
+ </button>
312
+ <button
313
+ onClick={() => onToggle(true)}
314
+ disabled={disabled}
315
+ className={clsx(
316
+ 'px-2 py-1 rounded transition-colors',
317
+ showAll ? 'bg-theme-hover text-theme-text-primary' : 'text-theme-text-secondary hover:text-theme-text-primary'
318
+ )}
319
+ >
320
+ All
321
+ </button>
322
+ </div>
323
+ )
324
+ }
325
+
326
+ // Simple JSON to YAML converter for display
327
+ function jsonToYaml(obj: Record<string, unknown>, indent = 0): string {
328
+ const spaces = ' '.repeat(indent)
329
+ let result = ''
330
+
331
+ for (const [key, value] of Object.entries(obj)) {
332
+ if (value === null || value === undefined) {
333
+ result += `${spaces}${key}: null\n`
334
+ } else if (typeof value === 'object' && !Array.isArray(value)) {
335
+ result += `${spaces}${key}:\n`
336
+ result += jsonToYaml(value as Record<string, unknown>, indent + 1)
337
+ } else if (Array.isArray(value)) {
338
+ result += `${spaces}${key}:\n`
339
+ for (const item of value) {
340
+ if (typeof item === 'object' && item !== null) {
341
+ result += `${spaces}- \n`
342
+ const itemYaml = jsonToYaml(item as Record<string, unknown>, indent + 2)
343
+ result += itemYaml
344
+ } else {
345
+ result += `${spaces}- ${formatValue(item)}\n`
346
+ }
347
+ }
348
+ } else {
349
+ result += `${spaces}${key}: ${formatValue(value)}\n`
350
+ }
351
+ }
352
+
353
+ return result
354
+ }
355
+
356
+ function formatValue(value: unknown): string {
357
+ if (typeof value === 'string') {
358
+ // Quote strings that contain special characters
359
+ if (value.includes(':') || value.includes('#') || value.includes('\n') || value.startsWith(' ') || value.endsWith(' ')) {
360
+ return `"${value.replace(/"/g, '\\"')}"`
361
+ }
362
+ return value
363
+ }
364
+ return String(value)
365
+ }
@@ -0,0 +1,37 @@
1
+ // Utility functions for Helm components
2
+
3
+ import { getHelmStatusColor } from '../../utils/badge-colors'
4
+ // Re-export formatAge from resource-utils to avoid duplication
5
+ export { formatAge } from '../resources/resource-utils'
6
+
7
+ // Get status color classes for Helm release status
8
+ // Delegates to centralized badge-colors for consistency
9
+ export function getStatusColor(status: string): string {
10
+ return getHelmStatusColor(status)
11
+ }
12
+
13
+ // Format date for display
14
+ export function formatDate(dateString: string): string {
15
+ const date = new Date(dateString)
16
+ return date.toLocaleString(undefined, {
17
+ year: 'numeric',
18
+ month: 'short',
19
+ day: 'numeric',
20
+ hour: '2-digit',
21
+ minute: '2-digit',
22
+ })
23
+ }
24
+
25
+ // Truncate text with ellipsis
26
+ export function truncate(text: string, maxLength: number): string {
27
+ if (text.length <= maxLength) return text
28
+ return text.slice(0, maxLength - 3) + '...'
29
+ }
30
+
31
+ // Get chart display name (combines chart name and version)
32
+ export function getChartDisplay(chart: string, version: string): string {
33
+ return `${chart}-${version}`
34
+ }
35
+
36
+ // Re-export kindToPlural from centralized navigation utils
37
+ export { kindToPlural } from '../../utils/navigation'
@@ -0,0 +1,262 @@
1
+ import { useMemo } from 'react'
2
+ import { clsx } from 'clsx'
3
+ import { Clock, ArrowRight, Shield } from 'lucide-react'
4
+ import { useChanges } from '../../api/client'
5
+ import { isChangeEvent } from '../../types'
6
+ import type { TimelineEvent } from '../../types'
7
+ import type { Topology } from '../../types'
8
+ import { useHasLimitedAccess } from '../../contexts/CapabilitiesContext'
9
+ import { buildResourceHierarchy, isProblematicEvent, type ResourceLane } from '../../utils/resource-hierarchy'
10
+ import { buildHealthSpans, timeToX } from '../timeline/shared'
11
+
12
+ interface ActivitySummaryProps {
13
+ namespaces: string[]
14
+ topology?: Topology | null
15
+ onNavigate: () => void
16
+ }
17
+
18
+ const MAX_LANES = 6
19
+ const SPAN_MINUTES = 60
20
+
21
+ const HEALTH_SPAN_COLORS: Record<string, string> = {
22
+ healthy: 'bg-green-500/50 dark:bg-green-600/50',
23
+ rolling: 'bg-blue-500/50 dark:bg-blue-500/50',
24
+ degraded: 'bg-amber-500/50 dark:bg-[#b8861e]',
25
+ unhealthy: 'bg-red-500/50 dark:bg-red-500/50',
26
+ }
27
+
28
+ // Simplified interestingness scoring for the mini view
29
+ function scoreLane(lane: ResourceLane): number {
30
+ const allEvents = [...lane.events, ...(lane.children?.flatMap(c => c.events) || [])]
31
+ let score = 0
32
+
33
+ const kindScores: Record<string, number> = {
34
+ Deployment: 50, Rollout: 50, StatefulSet: 50, DaemonSet: 50,
35
+ Service: 45, Ingress: 45, Gateway: 45,
36
+ HTTPRoute: 42, GRPCRoute: 42, TCPRoute: 42, TLSRoute: 42,
37
+ Job: 40, CronJob: 40,
38
+ Pod: 30, ReplicaSet: 20,
39
+ }
40
+ score += kindScores[lane.kind] || 15
41
+
42
+ // Problematic events are most important
43
+ score += allEvents.filter(e => isProblematicEvent(e)).length * 40
44
+
45
+ // Recency bonus
46
+ const fiveMinAgo = Date.now() - 5 * 60 * 1000
47
+ score += Math.min(allEvents.filter(e => new Date(e.timestamp).getTime() > fiveMinAgo).length * 30, 150)
48
+
49
+ // Children bonus
50
+ if (lane.children && lane.children.length > 0) score += 10
51
+
52
+ // System namespace penalty
53
+ if (['kube-system', 'kube-public', 'kube-node-lease'].includes(lane.namespace)) score -= 30
54
+
55
+ return score
56
+ }
57
+
58
+ // Short kind labels for compact display
59
+ const KIND_SHORT: Record<string, string> = {
60
+ Deployment: 'Deploy',
61
+ StatefulSet: 'SS',
62
+ DaemonSet: 'DS',
63
+ ReplicaSet: 'RS',
64
+ Service: 'Svc',
65
+ ConfigMap: 'CM',
66
+ CronJob: 'CJ',
67
+ Ingress: 'Ing',
68
+ Gateway: 'GW',
69
+ HTTPRoute: 'HR',
70
+ GRPCRoute: 'gRPC',
71
+ TCPRoute: 'TCP',
72
+ TLSRoute: 'TLS',
73
+ }
74
+
75
+ export function ActivitySummary({ namespaces, topology, onNavigate }: ActivitySummaryProps) {
76
+ const hasLimitedAccess = useHasLimitedAccess()
77
+ const { data: events, isLoading, error } = useChanges({
78
+ namespaces,
79
+ timeRange: '1h',
80
+ includeK8sEvents: true,
81
+ includeManaged: true,
82
+ limit: 1000,
83
+ })
84
+
85
+ const now = useMemo(() => Date.now(), [events])
86
+ const spanMs = SPAN_MINUTES * 60 * 1000
87
+ const startTime = now - spanMs
88
+
89
+ const lanes = useMemo(() => {
90
+ if (!events || events.length === 0) return []
91
+
92
+ // Only use events within the visible window
93
+ const windowEvents = events.filter(e => {
94
+ const t = new Date(e.timestamp).getTime()
95
+ return t >= startTime && t <= now
96
+ })
97
+ if (windowEvents.length === 0) return []
98
+
99
+ const hierarchy = buildResourceHierarchy({
100
+ events: windowEvents,
101
+ topology: topology || undefined,
102
+ })
103
+ return hierarchy
104
+ .sort((a, b) => scoreLane(b) - scoreLane(a))
105
+ .slice(0, MAX_LANES)
106
+ }, [events, startTime, now, topology])
107
+
108
+ const hasActivity = lanes.length > 0
109
+
110
+ return (
111
+ <button
112
+ onClick={onNavigate}
113
+ 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"
114
+ >
115
+ <div className="flex flex-col h-full w-full">
116
+ <div className="flex items-center justify-between px-5 py-3 border-b border-theme-border/50">
117
+ <div className="flex items-center gap-2">
118
+ <Clock className="w-4 h-4 text-theme-text-tertiary" />
119
+ <span className="text-xs font-semibold uppercase tracking-wider text-theme-text-secondary">Timeline</span>
120
+ </div>
121
+ <span className="text-xs text-theme-text-tertiary">last {SPAN_MINUTES}m</span>
122
+ </div>
123
+
124
+ {/* Mini swimlanes */}
125
+ <div className="flex-1 min-h-0 overflow-hidden px-4 py-1.5">
126
+ {isLoading ? (
127
+ <div className="flex items-center justify-center h-full py-4 text-xs text-theme-text-tertiary">
128
+ Loading...
129
+ </div>
130
+ ) : error ? (
131
+ <div className="flex items-center justify-center h-full py-4 text-xs text-theme-text-tertiary">
132
+ Could not load activity
133
+ </div>
134
+ ) : !hasActivity ? (
135
+ <div className="flex flex-col items-center justify-center h-full py-4 text-xs text-theme-text-tertiary">
136
+ <span>No recent activity</span>
137
+ {hasLimitedAccess && (
138
+ <span className="flex items-center gap-1 mt-1.5 text-[11px] text-amber-400/80">
139
+ <Shield className="w-3 h-3" />
140
+ Some resource types are not monitored due to RBAC restrictions
141
+ </span>
142
+ )}
143
+ </div>
144
+ ) : (
145
+ <div className="space-y-1">
146
+ {lanes.map((lane) => (
147
+ <MiniLane
148
+ key={lane.id}
149
+ lane={lane}
150
+ startTime={startTime}
151
+ now={now}
152
+ spanMs={spanMs}
153
+ />
154
+ ))}
155
+
156
+ {/* Time axis */}
157
+ <div className="flex items-center justify-between text-[10px] text-theme-text-tertiary pt-1">
158
+ <span>{SPAN_MINUTES}m ago</span>
159
+ <span>now</span>
160
+ </div>
161
+ </div>
162
+ )}
163
+ </div>
164
+
165
+ <div className="px-4 py-1.5 border-t border-theme-border/50 flex items-center justify-end gap-1.5 text-[10px] font-semibold uppercase tracking-wider text-theme-text-secondary group-hover:text-theme-text-primary transition-colors">
166
+ Open Timeline
167
+ <ArrowRight className="w-3.5 h-3.5 transition-transform group-hover:translate-x-0.5" />
168
+ </div>
169
+ </div>
170
+ </button>
171
+ )
172
+ }
173
+
174
+ // A single compact swimlane row: [kind label + name] [health bar with event dots]
175
+ function MiniLane({ lane, startTime, now, spanMs }: {
176
+ lane: ResourceLane
177
+ startTime: number
178
+ now: number
179
+ spanMs: number
180
+ }) {
181
+ const allEvents: TimelineEvent[] = lane.allEventsSorted || [
182
+ ...lane.events,
183
+ ...(lane.children?.flatMap(c => c.events) || []),
184
+ ]
185
+ const changeEvents = allEvents.filter(e => isChangeEvent(e))
186
+ const { spans } = buildHealthSpans(changeEvents, startTime, now, allEvents)
187
+
188
+ const hasProblems = allEvents.some(e => isProblematicEvent(e))
189
+ const kindLabel = KIND_SHORT[lane.kind] || lane.kind
190
+
191
+ return (
192
+ <div className="flex items-center gap-2 h-5">
193
+ {/* Label */}
194
+ <div className="w-[6.5rem] shrink-0 flex items-center gap-1 min-w-0">
195
+ <span className={clsx(
196
+ 'badge-sm shrink-0',
197
+ hasProblems
198
+ ? 'status-degraded'
199
+ : 'bg-theme-elevated text-theme-text-tertiary',
200
+ )}>
201
+ {kindLabel}
202
+ </span>
203
+ <span className="text-[11px] text-theme-text-secondary truncate">
204
+ {lane.name}
205
+ </span>
206
+ </div>
207
+
208
+ {/* Health bar track */}
209
+ <div className="flex-1 relative h-3 bg-theme-border/20 rounded-sm overflow-hidden">
210
+ {/* Health state spans */}
211
+ {spans.map((span, i) => {
212
+ const left = timeToX(span.start, startTime, spanMs)
213
+ const right = timeToX(span.end, startTime, spanMs)
214
+ const width = right - left
215
+ if (width <= 0 || right < 0 || left > 100) return null
216
+
217
+ const clampedLeft = Math.max(0, left)
218
+ const clampedWidth = Math.min(100 - clampedLeft, width - (clampedLeft - left))
219
+ if (clampedWidth <= 0) return null
220
+
221
+ return (
222
+ <div
223
+ key={i}
224
+ className={clsx(
225
+ 'absolute top-0 bottom-0 rounded-sm',
226
+ HEALTH_SPAN_COLORS[span.health] || 'bg-gray-400/30',
227
+ )}
228
+ style={{ left: `${clampedLeft}%`, width: `${clampedWidth}%` }}
229
+ />
230
+ )
231
+ })}
232
+
233
+ {/* All event dots — small for normal, larger for critical */}
234
+ {allEvents.map((event, idx) => {
235
+ const x = timeToX(new Date(event.timestamp).getTime(), startTime, spanMs)
236
+ if (x < 0 || x > 100) return null
237
+
238
+ const isCritical = isProblematicEvent(event)
239
+
240
+ return (
241
+ <div
242
+ key={`${event.id}-${idx}`}
243
+ className={clsx(
244
+ 'absolute top-1/2 -translate-y-1/2 -translate-x-1/2 rounded-full',
245
+ isCritical
246
+ ? 'w-2.5 h-2.5 bg-red-500 ring-1 ring-red-500/30 z-10'
247
+ : 'w-1.5 h-1.5',
248
+ !isCritical && (
249
+ event.eventType === 'add' ? 'bg-green-500'
250
+ : event.eventType === 'delete' ? 'bg-red-500'
251
+ : event.eventType === 'update' ? 'bg-blue-500'
252
+ : 'bg-theme-text-tertiary'
253
+ ),
254
+ )}
255
+ style={{ left: `${x}%` }}
256
+ />
257
+ )
258
+ })}
259
+ </div>
260
+ </div>
261
+ )
262
+ }