@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,91 @@
1
+ import { X, GitCompare } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+
4
+ interface ManifestDiffViewerProps {
5
+ diff: string
6
+ isLoading: boolean
7
+ revision1: number
8
+ revision2: number
9
+ onClose: () => void
10
+ }
11
+
12
+ export function ManifestDiffViewer({ diff, isLoading, revision1, revision2, onClose }: ManifestDiffViewerProps) {
13
+ if (isLoading) {
14
+ return (
15
+ <div className="flex items-center justify-center h-32 text-theme-text-tertiary">
16
+ Computing diff...
17
+ </div>
18
+ )
19
+ }
20
+
21
+ if (!diff) {
22
+ return (
23
+ <div className="p-4">
24
+ <div className="flex flex-col items-center justify-center h-32 text-theme-text-tertiary gap-2">
25
+ <GitCompare className="w-8 h-8 text-theme-text-disabled" />
26
+ <span>No differences found</span>
27
+ </div>
28
+ </div>
29
+ )
30
+ }
31
+
32
+ return (
33
+ <div className="p-4">
34
+ <div className="flex items-center justify-between mb-3">
35
+ <div className="flex items-center gap-2">
36
+ <GitCompare className="w-4 h-4 text-theme-text-secondary" />
37
+ <span className="text-sm font-medium text-theme-text-secondary">
38
+ Comparing Revision {revision1} → {revision2}
39
+ </span>
40
+ </div>
41
+ <button
42
+ onClick={onClose}
43
+ 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"
44
+ >
45
+ <X className="w-3.5 h-3.5" />
46
+ Close
47
+ </button>
48
+ </div>
49
+
50
+ <div className="rounded-lg overflow-hidden max-h-[calc(100vh-300px)] overflow-auto bg-theme-base/50 font-mono text-xs">
51
+ <div className="p-3">
52
+ {diff.split('\n').map((line, index) => (
53
+ <DiffLine key={index} line={line} />
54
+ ))}
55
+ </div>
56
+ </div>
57
+
58
+ {/* Legend */}
59
+ <div className="flex items-center gap-4 mt-3 text-xs text-theme-text-tertiary">
60
+ <div className="flex items-center gap-1">
61
+ <span className="w-3 h-3 bg-red-500/20 border border-red-500/50 rounded" />
62
+ <span>Removed</span>
63
+ </div>
64
+ <div className="flex items-center gap-1">
65
+ <span className="w-3 h-3 bg-green-500/20 border border-green-500/50 rounded" />
66
+ <span>Added</span>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ )
71
+ }
72
+
73
+ function DiffLine({ line }: { line: string }) {
74
+ const isAddition = line.startsWith('+') && !line.startsWith('+++')
75
+ const isRemoval = line.startsWith('-') && !line.startsWith('---')
76
+ const isHeader = line.startsWith('---') || line.startsWith('+++') || line.startsWith('@@')
77
+
78
+ return (
79
+ <div
80
+ className={clsx(
81
+ 'whitespace-pre',
82
+ isAddition && 'bg-green-500/10 text-green-400',
83
+ isRemoval && 'bg-red-500/10 text-red-400',
84
+ isHeader && 'text-theme-text-tertiary font-bold',
85
+ !isAddition && !isRemoval && !isHeader && 'text-theme-text-secondary'
86
+ )}
87
+ >
88
+ {line || ' '}
89
+ </div>
90
+ )
91
+ }
@@ -0,0 +1,61 @@
1
+ import { Copy, Check, Code } from 'lucide-react'
2
+ import { CodeViewer } from '../ui/CodeViewer'
3
+
4
+ interface ManifestViewerProps {
5
+ manifest: string
6
+ isLoading: boolean
7
+ revision?: number
8
+ onCopy: (text: string) => void
9
+ copied: boolean
10
+ }
11
+
12
+ export function ManifestViewer({ manifest, isLoading, revision, onCopy, copied }: ManifestViewerProps) {
13
+ if (isLoading) {
14
+ return (
15
+ <div className="flex items-center justify-center h-32 text-theme-text-tertiary">
16
+ Loading manifest...
17
+ </div>
18
+ )
19
+ }
20
+
21
+ if (!manifest) {
22
+ return (
23
+ <div className="flex flex-col items-center justify-center h-32 text-theme-text-tertiary gap-2">
24
+ <Code className="w-8 h-8 text-theme-text-disabled" />
25
+ <span>No manifest available</span>
26
+ </div>
27
+ )
28
+ }
29
+
30
+ const lineCount = manifest.split('\n').length
31
+
32
+ return (
33
+ <div className="p-4">
34
+ <div className="flex items-center justify-between mb-3">
35
+ <div className="flex items-center gap-2">
36
+ <span className="text-sm font-medium text-theme-text-secondary">Rendered Manifest</span>
37
+ {revision && (
38
+ <span className="badge bg-theme-elevated text-theme-text-secondary">
39
+ Revision {revision}
40
+ </span>
41
+ )}
42
+ <span className="text-xs text-theme-text-tertiary">{lineCount} lines</span>
43
+ </div>
44
+ <button
45
+ onClick={() => onCopy(manifest)}
46
+ 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"
47
+ >
48
+ {copied ? <Check className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
49
+ Copy
50
+ </button>
51
+ </div>
52
+
53
+ <CodeViewer
54
+ code={manifest}
55
+ language="yaml"
56
+ showLineNumbers
57
+ maxHeight="calc(100vh - 300px)"
58
+ />
59
+ </div>
60
+ )
61
+ }
@@ -0,0 +1,465 @@
1
+ import { useState, useCallback } from 'react'
2
+ import { Link2, ExternalLink, AlertCircle, Terminal, FileText, Plug, X, Loader2 } from 'lucide-react'
3
+ import { getResourceIcon } from '../../utils/resource-icons'
4
+ import { clsx } from 'clsx'
5
+ import type { HelmOwnedResource } from '../../types'
6
+ import type { NavigateToResource } from '../../utils/navigation'
7
+ import { kindToPlural } from '../../utils/navigation'
8
+ import { getResourceStatusColor, SEVERITY_BADGE } from '../../utils/badge-colors'
9
+ import { useQueryClient } from '@tanstack/react-query'
10
+ import { useOpenTerminal, useOpenLogs } from '../dock'
11
+ import { useStartPortForward } from '../portforward/PortForwardManager'
12
+ import { useAvailablePorts } from '../../api/client'
13
+ import { apiUrl, getAuthHeaders, getCredentialsMode } from '../../api/config'
14
+ import { useNamespacedCapabilities } from '../../contexts/CapabilitiesContext'
15
+
16
+ interface OwnedResourcesProps {
17
+ resources: HelmOwnedResource[]
18
+ onNavigate?: NavigateToResource
19
+ }
20
+
21
+ function getIconForKind(kind: string) {
22
+ return getResourceIcon(kind)
23
+ }
24
+
25
+ // Group resources by kind
26
+ function groupByKind(resources: HelmOwnedResource[]): Map<string, HelmOwnedResource[]> {
27
+ const groups = new Map<string, HelmOwnedResource[]>()
28
+ for (const resource of resources) {
29
+ const existing = groups.get(resource.kind) || []
30
+ existing.push(resource)
31
+ groups.set(resource.kind, existing)
32
+ }
33
+ return groups
34
+ }
35
+
36
+ // Health status types
37
+ type HealthFilter = 'all' | 'healthy' | 'warning' | 'error'
38
+
39
+ // Determine health status of a resource
40
+ function getResourceHealth(resource: HelmOwnedResource): 'healthy' | 'warning' | 'error' | 'unknown' {
41
+ // An issue field always indicates a problem regardless of status
42
+ if (resource.issue) return 'error'
43
+ const status = resource.status
44
+ if (!status) return 'unknown'
45
+ const s = status.toLowerCase()
46
+ if (['running', 'active', 'succeeded', 'bound', 'available'].includes(s)) return 'healthy'
47
+ if (['pending', 'progressing', 'scaled to 0', 'suspended', 'creating'].includes(s)) return 'warning'
48
+ if (['failed', 'error', 'crashloopbackoff', 'imagepullbackoff', 'evicted', 'terminating'].includes(s)) return 'error'
49
+ return 'unknown'
50
+ }
51
+
52
+ // Compute health summary
53
+ function computeHealthSummary(resources: HelmOwnedResource[]) {
54
+ let healthy = 0
55
+ let warning = 0
56
+ let error = 0
57
+ let unknown = 0
58
+
59
+ for (const r of resources) {
60
+ const health = getResourceHealth(r)
61
+ if (health === 'healthy') healthy++
62
+ else if (health === 'warning') warning++
63
+ else if (health === 'error') error++
64
+ else unknown++
65
+ }
66
+
67
+ return { healthy, warning, error, unknown, total: resources.length }
68
+ }
69
+
70
+ export function OwnedResources({ resources, onNavigate }: OwnedResourcesProps) {
71
+ const [healthFilter, setHealthFilter] = useState<HealthFilter>('all')
72
+
73
+ if (!resources || resources.length === 0) {
74
+ return (
75
+ <div className="flex flex-col items-center justify-center h-32 text-theme-text-tertiary gap-2">
76
+ <Link2 className="w-8 h-8 text-theme-text-disabled" />
77
+ <span>No owned resources</span>
78
+ </div>
79
+ )
80
+ }
81
+
82
+ const health = computeHealthSummary(resources)
83
+
84
+ // Filter resources by health status
85
+ const filteredResources = healthFilter === 'all'
86
+ ? resources
87
+ : resources.filter(r => getResourceHealth(r) === healthFilter)
88
+
89
+ const grouped = groupByKind(filteredResources)
90
+
91
+ const handleFilterClick = (filter: HealthFilter) => {
92
+ setHealthFilter(prev => prev === filter ? 'all' : filter)
93
+ }
94
+
95
+ return (
96
+ <div className="p-4 space-y-4">
97
+ {/* Health summary - clickable badges */}
98
+ <div className="flex items-center justify-between">
99
+ <div className="text-sm text-theme-text-secondary">
100
+ {healthFilter === 'all' ? (
101
+ <>{resources.length} resource{resources.length !== 1 ? 's' : ''} created by this release</>
102
+ ) : (
103
+ <span className="flex items-center gap-2">
104
+ Showing {filteredResources.length} of {resources.length} resources
105
+ <button
106
+ onClick={() => setHealthFilter('all')}
107
+ className="p-0.5 text-theme-text-tertiary hover:text-theme-text-primary hover:bg-theme-elevated rounded"
108
+ title="Clear filter"
109
+ >
110
+ <X className="w-3.5 h-3.5" />
111
+ </button>
112
+ </span>
113
+ )}
114
+ </div>
115
+ <div className="flex items-center gap-2">
116
+ {health.healthy > 0 && (
117
+ <button
118
+ onClick={() => handleFilterClick('healthy')}
119
+ className={clsx(
120
+ 'flex items-center gap-1 px-2 py-0.5 text-xs rounded transition-all',
121
+ SEVERITY_BADGE.success,
122
+ healthFilter === 'healthy' && 'ring-2 ring-green-400/50 ring-offset-1 ring-offset-theme-surface'
123
+ )}
124
+ >
125
+ {health.healthy} healthy
126
+ </button>
127
+ )}
128
+ {health.warning > 0 && (
129
+ <button
130
+ onClick={() => handleFilterClick('warning')}
131
+ className={clsx(
132
+ 'flex items-center gap-1 px-2 py-0.5 text-xs rounded transition-all',
133
+ SEVERITY_BADGE.warning,
134
+ healthFilter === 'warning' && 'ring-2 ring-amber-400/50 ring-offset-1 ring-offset-theme-surface'
135
+ )}
136
+ >
137
+ {health.warning} pending
138
+ </button>
139
+ )}
140
+ {health.error > 0 && (
141
+ <button
142
+ onClick={() => handleFilterClick('error')}
143
+ className={clsx(
144
+ 'flex items-center gap-1 px-2 py-0.5 text-xs rounded transition-all',
145
+ SEVERITY_BADGE.error,
146
+ healthFilter === 'error' && 'ring-2 ring-red-400/50 ring-offset-1 ring-offset-theme-surface'
147
+ )}
148
+ >
149
+ {health.error} failed
150
+ </button>
151
+ )}
152
+ </div>
153
+ </div>
154
+
155
+ {filteredResources.length === 0 ? (
156
+ <div className="flex flex-col items-center justify-center h-32 text-theme-text-tertiary gap-2">
157
+ <AlertCircle className="w-8 h-8 text-theme-text-disabled" />
158
+ <span>No resources match the selected filter</span>
159
+ <button
160
+ onClick={() => setHealthFilter('all')}
161
+ className="text-xs text-blue-400 hover:text-blue-300"
162
+ >
163
+ Clear filter
164
+ </button>
165
+ </div>
166
+ ) : (
167
+ Array.from(grouped.entries()).map(([kind, items]) => {
168
+ const Icon = getIconForKind(kind)
169
+
170
+ return (
171
+ <div key={kind} className="bg-theme-elevated/30 rounded-lg p-3">
172
+ <div className="flex items-center gap-2 mb-2">
173
+ <Icon className="w-4 h-4 text-theme-text-secondary" />
174
+ <span className="text-sm font-medium text-theme-text-secondary">{kind}</span>
175
+ <span className="text-xs text-theme-text-tertiary">({items.length})</span>
176
+ </div>
177
+ <div className="space-y-1">
178
+ {items.map((resource, idx) => (
179
+ <ResourceItem
180
+ key={`${resource.namespace}-${resource.name}-${idx}`}
181
+ resource={resource}
182
+ onNavigate={onNavigate}
183
+ />
184
+ ))}
185
+ </div>
186
+ </div>
187
+ )
188
+ })
189
+ )}
190
+ </div>
191
+ )
192
+ }
193
+
194
+ interface ResourceItemProps {
195
+ resource: HelmOwnedResource
196
+ onNavigate?: NavigateToResource
197
+ }
198
+
199
+ function ResourceItem({ resource, onNavigate }: ResourceItemProps) {
200
+ const canNavigate = !!onNavigate
201
+ const isPod = resource.kind.toLowerCase() === 'pod'
202
+ const isService = resource.kind.toLowerCase() === 'service'
203
+ const isRunning = resource.status?.toLowerCase() === 'running'
204
+
205
+ const handleClick = () => {
206
+ if (onNavigate) {
207
+ onNavigate({ kind: kindToPlural(resource.kind), namespace: resource.namespace, name: resource.name })
208
+ }
209
+ }
210
+
211
+ const isError = resource.status && ['failed', 'error', 'crashloopbackoff', 'imagepullbackoff', 'evicted'].includes(resource.status.toLowerCase())
212
+
213
+ return (
214
+ <div
215
+ className={clsx(
216
+ 'flex items-center justify-between p-2 rounded text-sm group',
217
+ canNavigate
218
+ ? 'cursor-pointer hover:bg-theme-elevated/50'
219
+ : 'bg-theme-surface/50'
220
+ )}
221
+ >
222
+ <div
223
+ onClick={canNavigate ? handleClick : undefined}
224
+ className="flex items-center gap-2 min-w-0 flex-1"
225
+ >
226
+ <span className="text-theme-text-primary truncate">{resource.name}</span>
227
+ {resource.namespace && (
228
+ <span className="text-xs text-theme-text-tertiary shrink-0">{resource.namespace}</span>
229
+ )}
230
+ </div>
231
+
232
+ <div className="flex items-center gap-2 shrink-0">
233
+ {/* Quick actions for pods */}
234
+ {isPod && (
235
+ <PodQuickActions
236
+ namespace={resource.namespace}
237
+ podName={resource.name}
238
+ isRunning={isRunning}
239
+ />
240
+ )}
241
+
242
+ {/* Quick actions for services */}
243
+ {isService && (
244
+ <ServiceQuickActions
245
+ namespace={resource.namespace}
246
+ serviceName={resource.name}
247
+ />
248
+ )}
249
+
250
+ {/* Ready count (e.g., 3/3) */}
251
+ {resource.ready && (
252
+ <span className="text-xs text-theme-text-secondary font-mono">{resource.ready}</span>
253
+ )}
254
+
255
+ {/* Status badge */}
256
+ {resource.status && (
257
+ <span
258
+ className={clsx('badge-sm', getResourceStatusColor(resource.status || ''))}
259
+ title={resource.message || resource.status}
260
+ >
261
+ {resource.status}
262
+ </span>
263
+ )}
264
+
265
+ {/* Issue summary (e.g., "OOMKilled", "CrashLoopBackOff") */}
266
+ {resource.issue && (
267
+ <span
268
+ className="text-xs text-red-400"
269
+ title={resource.summary || resource.issue}
270
+ >
271
+ {resource.issue}
272
+ </span>
273
+ )}
274
+
275
+ {/* Error icon with message tooltip */}
276
+ {isError && resource.message && !resource.issue && (
277
+ <span title={resource.message}>
278
+ <AlertCircle className="w-3.5 h-3.5 text-red-400" />
279
+ </span>
280
+ )}
281
+
282
+ {canNavigate && (
283
+ <button
284
+ onClick={handleClick}
285
+ className="p-1 text-theme-text-tertiary opacity-0 group-hover:opacity-100 hover:text-theme-text-primary hover:bg-theme-elevated rounded transition-all"
286
+ title="View details"
287
+ >
288
+ <ExternalLink className="w-3.5 h-3.5" />
289
+ </button>
290
+ )}
291
+ </div>
292
+ </div>
293
+ )
294
+ }
295
+
296
+ // Quick actions for pods
297
+ interface PodQuickActionsProps {
298
+ namespace: string
299
+ podName: string
300
+ isRunning: boolean
301
+ }
302
+
303
+ function PodQuickActions({ namespace, podName, isRunning }: PodQuickActionsProps) {
304
+ const queryClient = useQueryClient()
305
+ const openTerminal = useOpenTerminal()
306
+ const openLogs = useOpenLogs()
307
+ const startPortForward = useStartPortForward()
308
+ const { data: portsData, isLoading: portsLoading } = useAvailablePorts('pod', namespace, podName)
309
+
310
+ // Capabilities (namespace-scoped: re-checks RBAC if globally denied)
311
+ const { canExec, canViewLogs, canPortForward } = useNamespacedCapabilities(namespace)
312
+
313
+ const [isLoadingAction, setIsLoadingAction] = useState(false)
314
+
315
+ // Fetch pod data using React Query cache - shared with resource views
316
+ const fetchPodData = useCallback(async () => {
317
+ return queryClient.fetchQuery({
318
+ queryKey: ['resource', 'pods', namespace, podName],
319
+ queryFn: async () => {
320
+ const response = await fetch(apiUrl(`/resources/pods/${namespace}/${podName}`), {
321
+ credentials: getCredentialsMode(),
322
+ headers: getAuthHeaders(),
323
+ })
324
+ if (!response.ok) throw new Error('Failed to fetch pod')
325
+ return response.json()
326
+ },
327
+ staleTime: 30000,
328
+ })
329
+ }, [queryClient, namespace, podName])
330
+
331
+ const handleOpenTerminal = useCallback(async () => {
332
+ if (!isRunning) return
333
+ setIsLoadingAction(true)
334
+ try {
335
+ const data = await fetchPodData()
336
+ const containers = data.resource?.spec?.containers || []
337
+ if (containers.length > 0) {
338
+ openTerminal({
339
+ namespace,
340
+ podName,
341
+ containerName: containers[0].name,
342
+ containers: containers.map((c: { name: string }) => c.name),
343
+ })
344
+ }
345
+ } catch (error) {
346
+ console.error('Failed to open terminal:', error)
347
+ } finally {
348
+ setIsLoadingAction(false)
349
+ }
350
+ }, [namespace, podName, isRunning, openTerminal, fetchPodData])
351
+
352
+ const handleOpenLogs = useCallback(async () => {
353
+ setIsLoadingAction(true)
354
+ try {
355
+ const data = await fetchPodData()
356
+ const containers = data.resource?.spec?.containers || []
357
+ openLogs({
358
+ namespace,
359
+ podName,
360
+ containers: containers.map((c: { name: string }) => c.name),
361
+ })
362
+ } catch (error) {
363
+ console.error('Failed to open logs:', error)
364
+ } finally {
365
+ setIsLoadingAction(false)
366
+ }
367
+ }, [namespace, podName, openLogs, fetchPodData])
368
+
369
+ const handlePortForward = useCallback((port: number) => {
370
+ startPortForward.mutate({
371
+ namespace,
372
+ podName,
373
+ podPort: port,
374
+ })
375
+ }, [namespace, podName, startPortForward])
376
+
377
+ const ports = portsData?.ports || []
378
+
379
+ return (
380
+ <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
381
+ {isLoadingAction && <Loader2 className="w-3.5 h-3.5 animate-spin text-theme-text-tertiary" />}
382
+
383
+ {/* Terminal */}
384
+ {isRunning && canExec && (
385
+ <button
386
+ onClick={(e) => { e.stopPropagation(); handleOpenTerminal() }}
387
+ disabled={isLoadingAction}
388
+ className="p-1 text-theme-text-tertiary hover:text-blue-400 hover:bg-blue-500/10 rounded transition-colors disabled:opacity-50"
389
+ title="Open terminal"
390
+ >
391
+ <Terminal className="w-3.5 h-3.5" />
392
+ </button>
393
+ )}
394
+
395
+ {/* Logs */}
396
+ {canViewLogs && (
397
+ <button
398
+ onClick={(e) => { e.stopPropagation(); handleOpenLogs() }}
399
+ disabled={isLoadingAction}
400
+ className="p-1 text-theme-text-tertiary hover:text-blue-400 hover:bg-blue-500/10 rounded transition-colors disabled:opacity-50"
401
+ title="View logs"
402
+ >
403
+ <FileText className="w-3.5 h-3.5" />
404
+ </button>
405
+ )}
406
+
407
+ {/* Port Forward */}
408
+ {canPortForward && !portsLoading && ports.length > 0 && (
409
+ <button
410
+ onClick={(e) => { e.stopPropagation(); handlePortForward(ports[0].port) }}
411
+ disabled={startPortForward.isPending}
412
+ className="p-1 text-theme-text-tertiary hover:text-blue-400 hover:bg-blue-500/10 rounded transition-colors disabled:opacity-50"
413
+ title={`Port forward :${ports[0].port}`}
414
+ >
415
+ {startPortForward.isPending ? (
416
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
417
+ ) : (
418
+ <Plug className="w-3.5 h-3.5" />
419
+ )}
420
+ </button>
421
+ )}
422
+ </div>
423
+ )
424
+ }
425
+
426
+ // Quick actions for services
427
+ interface ServiceQuickActionsProps {
428
+ namespace: string
429
+ serviceName: string
430
+ }
431
+
432
+ function ServiceQuickActions({ namespace, serviceName }: ServiceQuickActionsProps) {
433
+ const startPortForward = useStartPortForward()
434
+ const { data: portsData, isLoading: portsLoading } = useAvailablePorts('service', namespace, serviceName)
435
+ const { canPortForward } = useNamespacedCapabilities(namespace)
436
+
437
+ const handlePortForward = useCallback((port: number) => {
438
+ startPortForward.mutate({
439
+ namespace,
440
+ serviceName,
441
+ podPort: port,
442
+ })
443
+ }, [namespace, serviceName, startPortForward])
444
+
445
+ const ports = portsData?.ports || []
446
+
447
+ if (!canPortForward || portsLoading || ports.length === 0) return null
448
+
449
+ return (
450
+ <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
451
+ <button
452
+ onClick={(e) => { e.stopPropagation(); handlePortForward(ports[0].port) }}
453
+ disabled={startPortForward.isPending}
454
+ className="p-1 text-theme-text-tertiary hover:text-blue-400 hover:bg-blue-500/10 rounded transition-colors disabled:opacity-50"
455
+ title={`Port forward :${ports[0].port}`}
456
+ >
457
+ {startPortForward.isPending ? (
458
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
459
+ ) : (
460
+ <Plug className="w-3.5 h-3.5" />
461
+ )}
462
+ </button>
463
+ </div>
464
+ )
465
+ }