@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,167 @@
1
+ import { useState } from 'react'
2
+ import { History, Eye, GitCompare, Check, RotateCcw } from 'lucide-react'
3
+ import { clsx } from 'clsx'
4
+ import type { HelmRevision } from '../../types'
5
+ import { getStatusColor, formatDate, formatAge } from './helm-utils'
6
+ import { SEVERITY_BADGE } from '../../utils/badge-colors'
7
+
8
+ interface RevisionHistoryProps {
9
+ history: HelmRevision[]
10
+ currentRevision: number
11
+ onViewRevision: (revision: number) => void
12
+ onCompare: (rev1: number, rev2: number) => void
13
+ onRollback?: (revision: number) => void
14
+ }
15
+
16
+ export function RevisionHistory({ history, currentRevision, onViewRevision, onCompare, onRollback }: RevisionHistoryProps) {
17
+ const [selectedForCompare, setSelectedForCompare] = useState<number | null>(null)
18
+
19
+ const handleCompareClick = (revision: number) => {
20
+ if (selectedForCompare === null) {
21
+ setSelectedForCompare(revision)
22
+ } else if (selectedForCompare === revision) {
23
+ setSelectedForCompare(null)
24
+ } else {
25
+ // Compare the two revisions (older first)
26
+ const rev1 = Math.min(selectedForCompare, revision)
27
+ const rev2 = Math.max(selectedForCompare, revision)
28
+ onCompare(rev1, rev2)
29
+ setSelectedForCompare(null)
30
+ }
31
+ }
32
+
33
+ if (!history || history.length === 0) {
34
+ return (
35
+ <div className="flex flex-col items-center justify-center h-32 text-theme-text-tertiary gap-2">
36
+ <History className="w-8 h-8 text-theme-text-disabled" />
37
+ <span>No revision history</span>
38
+ </div>
39
+ )
40
+ }
41
+
42
+ return (
43
+ <div className="p-4">
44
+ {/* Compare mode indicator */}
45
+ {selectedForCompare !== null && (
46
+ <div className="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg flex items-center justify-between">
47
+ <div className="flex items-center gap-2 text-sm text-blue-300">
48
+ <GitCompare className="w-4 h-4" />
49
+ <span>Select another revision to compare with revision {selectedForCompare}</span>
50
+ </div>
51
+ <button
52
+ onClick={() => setSelectedForCompare(null)}
53
+ className="text-xs text-theme-text-secondary hover:text-theme-text-primary"
54
+ >
55
+ Cancel
56
+ </button>
57
+ </div>
58
+ )}
59
+
60
+ {/* History timeline */}
61
+ <div className="space-y-2">
62
+ {history.map((revision, index) => {
63
+ const isCurrent = revision.revision === currentRevision
64
+ const isSelectedForCompare = selectedForCompare === revision.revision
65
+
66
+ return (
67
+ <div
68
+ key={revision.revision}
69
+ className={clsx(
70
+ 'relative bg-theme-elevated/30 rounded-lg p-4 border transition-colors',
71
+ isCurrent
72
+ ? 'border-green-500/50'
73
+ : isSelectedForCompare
74
+ ? 'border-blue-500/50 bg-blue-500/10'
75
+ : 'border-transparent hover:border-theme-border-light'
76
+ )}
77
+ >
78
+ {/* Timeline connector */}
79
+ {index < history.length - 1 && (
80
+ <div className="absolute left-7 top-14 bottom-0 w-0.5 bg-theme-hover -mb-2" />
81
+ )}
82
+
83
+ <div className="flex items-start gap-3">
84
+ {/* Revision circle */}
85
+ <div
86
+ className={clsx(
87
+ 'w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold shrink-0 z-10',
88
+ isCurrent
89
+ ? 'bg-green-500 text-theme-text-primary'
90
+ : 'bg-theme-hover text-theme-text-secondary'
91
+ )}
92
+ >
93
+ {revision.revision}
94
+ </div>
95
+
96
+ {/* Revision details */}
97
+ <div className="flex-1 min-w-0">
98
+ <div className="flex items-center gap-2 flex-wrap">
99
+ <span className="text-sm font-medium text-theme-text-primary">{revision.chart}</span>
100
+ <span className={clsx('badge-sm', getStatusColor(revision.status))}>
101
+ {revision.status}
102
+ </span>
103
+ {isCurrent && (
104
+ <span className={clsx('badge-sm', SEVERITY_BADGE.success)}>
105
+ Current
106
+ </span>
107
+ )}
108
+ </div>
109
+
110
+ <div className="flex items-center gap-4 mt-1 text-xs text-theme-text-tertiary">
111
+ <span title={revision.updated}>{formatDate(revision.updated)}</span>
112
+ <span>{formatAge(revision.updated)} ago</span>
113
+ </div>
114
+
115
+ {revision.description && (
116
+ <p className="mt-2 text-sm text-theme-text-secondary truncate">{revision.description}</p>
117
+ )}
118
+
119
+ {/* Actions */}
120
+ <div className="flex items-center gap-2 mt-3">
121
+ <button
122
+ onClick={() => onViewRevision(revision.revision)}
123
+ 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"
124
+ >
125
+ <Eye className="w-3.5 h-3.5" />
126
+ View Manifest
127
+ </button>
128
+ <button
129
+ onClick={() => handleCompareClick(revision.revision)}
130
+ className={clsx(
131
+ 'flex items-center gap-1 px-2 py-1 text-xs rounded',
132
+ isSelectedForCompare
133
+ ? 'bg-blue-500 text-theme-text-primary'
134
+ : 'text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated'
135
+ )}
136
+ >
137
+ {isSelectedForCompare ? (
138
+ <>
139
+ <Check className="w-3.5 h-3.5" />
140
+ Selected
141
+ </>
142
+ ) : (
143
+ <>
144
+ <GitCompare className="w-3.5 h-3.5" />
145
+ Compare
146
+ </>
147
+ )}
148
+ </button>
149
+ {!isCurrent && onRollback && (
150
+ <button
151
+ onClick={() => onRollback(revision.revision)}
152
+ className="flex items-center gap-1 px-2 py-1 text-xs text-amber-400 hover:text-amber-300 hover:bg-amber-500/10 rounded"
153
+ >
154
+ <RotateCcw className="w-3.5 h-3.5" />
155
+ Rollback
156
+ </button>
157
+ )}
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ )
163
+ })}
164
+ </div>
165
+ </div>
166
+ )
167
+ }
@@ -0,0 +1,190 @@
1
+ import { useEffect, useRef } from 'react'
2
+ import { X, Play, Loader2, FileText, AlertTriangle } from 'lucide-react'
3
+ import { clsx } from 'clsx'
4
+ import type { ValuesPreviewResponse } from '../../types'
5
+
6
+ interface ValuesDiffPreviewProps {
7
+ previewData: ValuesPreviewResponse
8
+ onClose: () => void
9
+ onApply: () => void
10
+ isApplying: boolean
11
+ }
12
+
13
+ export function ValuesDiffPreview({
14
+ previewData,
15
+ onClose,
16
+ onApply,
17
+ isApplying,
18
+ }: ValuesDiffPreviewProps) {
19
+ const dialogRef = useRef<HTMLDivElement>(null)
20
+
21
+ // Handle ESC key
22
+ useEffect(() => {
23
+ const handleKeyDown = (e: KeyboardEvent) => {
24
+ if (e.key === 'Escape' && !isApplying) {
25
+ e.stopPropagation()
26
+ onClose()
27
+ }
28
+ }
29
+ document.addEventListener('keydown', handleKeyDown, true)
30
+ return () => document.removeEventListener('keydown', handleKeyDown, true)
31
+ }, [onClose, isApplying])
32
+
33
+ // Focus trap
34
+ useEffect(() => {
35
+ if (dialogRef.current) {
36
+ dialogRef.current.focus()
37
+ }
38
+ }, [])
39
+
40
+ const hasChanges = previewData.manifestDiff.trim().length > 0 &&
41
+ previewData.manifestDiff.split('\n').some(line => line.startsWith('+') || line.startsWith('-'))
42
+
43
+ return (
44
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
45
+ {/* Backdrop */}
46
+ <div
47
+ className="absolute inset-0 bg-black/60 backdrop-blur-sm"
48
+ onClick={isApplying ? undefined : onClose}
49
+ />
50
+
51
+ {/* Dialog */}
52
+ <div
53
+ ref={dialogRef}
54
+ tabIndex={-1}
55
+ className="relative dialog max-w-4xl w-full mx-4 max-h-[85vh] flex flex-col outline-none"
56
+ >
57
+ {/* Header */}
58
+ <div className="flex items-center justify-between px-4 py-3 border-b border-theme-border shrink-0">
59
+ <div className="flex items-center gap-2">
60
+ <FileText className="w-5 h-5 text-blue-400" />
61
+ <h3 className="text-lg font-semibold text-theme-text-primary">Preview Changes</h3>
62
+ </div>
63
+ <button
64
+ onClick={onClose}
65
+ disabled={isApplying}
66
+ className="p-1.5 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded disabled:opacity-50"
67
+ >
68
+ <X className="w-5 h-5" />
69
+ </button>
70
+ </div>
71
+
72
+ {/* Content */}
73
+ <div className="flex-1 overflow-auto p-4">
74
+ {hasChanges ? (
75
+ <div className="space-y-4">
76
+ <p className="text-sm text-theme-text-secondary">
77
+ The following manifest changes will be applied:
78
+ </p>
79
+ <DiffView diff={previewData.manifestDiff} />
80
+ </div>
81
+ ) : (
82
+ <div className="flex flex-col items-center justify-center py-12 text-theme-text-tertiary">
83
+ <AlertTriangle className="w-12 h-12 mb-3 text-amber-400/50" />
84
+ <p className="text-lg font-medium text-theme-text-secondary">No Manifest Changes</p>
85
+ <p className="text-sm mt-1">
86
+ The new values will not change the rendered manifest.
87
+ </p>
88
+ </div>
89
+ )}
90
+ </div>
91
+
92
+ {/* Footer */}
93
+ <div className="flex items-center justify-between px-4 py-3 border-t border-theme-border shrink-0 bg-theme-surface/50">
94
+ <div className="text-xs text-theme-text-tertiary">
95
+ {hasChanges ? (
96
+ <span className="flex items-center gap-1">
97
+ <span className="text-green-400">+</span> additions
98
+ <span className="mx-2">|</span>
99
+ <span className="text-red-400">-</span> deletions
100
+ </span>
101
+ ) : (
102
+ <span>Values will be updated without manifest changes</span>
103
+ )}
104
+ </div>
105
+ <div className="flex items-center gap-3">
106
+ <button
107
+ onClick={onClose}
108
+ disabled={isApplying}
109
+ className="px-4 py-2 text-sm font-medium text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded-lg transition-colors disabled:opacity-50"
110
+ >
111
+ Cancel
112
+ </button>
113
+ <button
114
+ onClick={onApply}
115
+ disabled={isApplying}
116
+ className="flex items-center gap-2 px-4 py-2 text-sm font-medium btn-brand rounded-lg"
117
+ >
118
+ {isApplying ? (
119
+ <Loader2 className="w-4 h-4 animate-spin" />
120
+ ) : (
121
+ <Play className="w-4 h-4" />
122
+ )}
123
+ Apply Changes
124
+ </button>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ )
130
+ }
131
+
132
+ interface DiffViewProps {
133
+ diff: string
134
+ }
135
+
136
+ function DiffView({ diff }: DiffViewProps) {
137
+ const lines = diff.split('\n')
138
+
139
+ return (
140
+ <div className="rounded-lg border border-theme-border overflow-hidden">
141
+ <div className="bg-theme-elevated/50 px-3 py-2 text-xs font-medium text-theme-text-secondary border-b border-theme-border">
142
+ Manifest Diff
143
+ </div>
144
+ <div className="overflow-auto max-h-[50vh]">
145
+ <pre className="text-xs font-mono p-0 m-0">
146
+ {lines.map((line, index) => (
147
+ <DiffLine key={index} line={line} lineNumber={index + 1} />
148
+ ))}
149
+ </pre>
150
+ </div>
151
+ </div>
152
+ )
153
+ }
154
+
155
+ interface DiffLineProps {
156
+ line: string
157
+ lineNumber: number
158
+ }
159
+
160
+ function DiffLine({ line, lineNumber }: DiffLineProps) {
161
+ const isAddition = line.startsWith('+') && !line.startsWith('+++')
162
+ const isDeletion = line.startsWith('-') && !line.startsWith('---')
163
+ const isHeader = line.startsWith('@@') || line.startsWith('---') || line.startsWith('+++')
164
+
165
+ return (
166
+ <div
167
+ className={clsx(
168
+ 'flex',
169
+ isAddition && 'bg-green-500/10',
170
+ isDeletion && 'bg-red-500/10',
171
+ isHeader && 'bg-blue-500/10'
172
+ )}
173
+ >
174
+ <span className="w-12 shrink-0 text-right pr-3 py-0.5 text-theme-text-disabled select-none border-r border-theme-border/50">
175
+ {lineNumber}
176
+ </span>
177
+ <span
178
+ className={clsx(
179
+ 'flex-1 px-3 py-0.5 whitespace-pre',
180
+ isAddition && 'text-green-400',
181
+ isDeletion && 'text-red-400',
182
+ isHeader && 'text-blue-400 font-medium',
183
+ !isAddition && !isDeletion && !isHeader && 'text-theme-text-secondary'
184
+ )}
185
+ >
186
+ {line}
187
+ </span>
188
+ </div>
189
+ )
190
+ }