@skyhook-io/radar-app 1.16.3 → 1.17.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 (321) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/App.tsx +138 -93
  4. package/src/RadarApp.tsx +27 -17
  5. package/src/api/actions.errorCode.test.ts +40 -0
  6. package/src/api/actions.test.ts +48 -0
  7. package/src/api/actions.ts +79 -0
  8. package/src/api/apiResources.navigation.test.tsx +232 -0
  9. package/src/api/apiResources.ts +46 -3
  10. package/src/api/client.goneResource.test.ts +49 -0
  11. package/src/api/client.helmSource.test.ts +39 -0
  12. package/src/api/client.namespaceSwitch.hook.test.tsx +54 -0
  13. package/src/api/client.namespaceSwitch.test.ts +107 -0
  14. package/src/api/client.ts +381 -47
  15. package/src/api/cnpg-ha.live.test.ts +140 -0
  16. package/src/api/cnpg-ha.test.ts +78 -0
  17. package/src/api/cnpg-ha.ts +19 -0
  18. package/src/api/cnpg-history.ts +123 -0
  19. package/src/api/cnpg-inspect.ts +102 -0
  20. package/src/api/cnpg-protection.ts +112 -0
  21. package/src/api/cnpg-recovery.test.tsx +60 -0
  22. package/src/api/cnpg-recovery.ts +262 -0
  23. package/src/api/cnpg-sessions.ts +175 -0
  24. package/src/api/cnpg-storage.ts +138 -0
  25. package/src/api/cnpg.legacy-reads.test.ts +43 -0
  26. package/src/api/cnpg.pooler-runtime.test.ts +15 -0
  27. package/src/api/cnpg.ts +428 -10
  28. package/src/api/diagnose.test.ts +40 -0
  29. package/src/api/diagnose.ts +12 -1
  30. package/src/api/radarFeatures.render.test.tsx +65 -3
  31. package/src/api/radarFeatures.ts +6 -0
  32. package/src/api/reviewedActionMutation.test.tsx +164 -0
  33. package/src/api/reviewedActionMutation.ts +66 -0
  34. package/src/api/timelineSource.test.ts +9 -0
  35. package/src/api/timelineSource.ts +8 -0
  36. package/src/api/traffic.sources.test.ts +55 -0
  37. package/src/api/traffic.ts +173 -14
  38. package/src/components/applications/ApplicationsView.tsx +3 -0
  39. package/src/components/audit/AuditView.tsx +9 -0
  40. package/src/components/capacity/CapacityActivity.tsx +3 -31
  41. package/src/components/capacity/CapacityDemand.tsx +4 -36
  42. package/src/components/capacity/CapacityOverview.tsx +217 -56
  43. package/src/components/capacity/CapacityPoolDetail.tsx +24 -62
  44. package/src/components/capacity/CapacityView.test.tsx +354 -1
  45. package/src/components/capacity/ClusterSchedulingCard.tsx +2 -10
  46. package/src/components/capacity/certaintyGlyph.test.tsx +2 -9
  47. package/src/components/capacity/routes.ts +42 -0
  48. package/src/components/capacity/shared.tsx +23 -150
  49. package/src/components/capacity/workspaceRoute.ts +18 -0
  50. package/src/components/cnpg/CNPGActionHost.compatibility.test.tsx +30 -0
  51. package/src/components/cnpg/CNPGArchivingRepair.tsx +191 -0
  52. package/src/components/cnpg/CNPGBlockingSessions.test.tsx +50 -0
  53. package/src/components/cnpg/CNPGBlockingSessions.tsx +333 -0
  54. package/src/components/cnpg/CNPGClusterActivity.test.tsx +11 -0
  55. package/src/components/cnpg/CNPGClusterActivity.tsx +18 -9
  56. package/src/components/cnpg/CNPGClusterLogs.compatibility.test.tsx +130 -0
  57. package/src/components/cnpg/CNPGClusterLogs.empty.test.tsx +29 -0
  58. package/src/components/cnpg/CNPGClusterLogs.test.ts +9 -0
  59. package/src/components/cnpg/CNPGClusterLogs.tsx +81 -24
  60. package/src/components/cnpg/CNPGClusterTabs.test.tsx +152 -0
  61. package/src/components/cnpg/CNPGClusterTabs.tsx +187 -0
  62. package/src/components/cnpg/CNPGClustersKindView.tsx +46 -0
  63. package/src/components/cnpg/CNPGConfiguration.test.tsx +223 -0
  64. package/src/components/cnpg/CNPGConfiguration.tsx +190 -0
  65. package/src/components/cnpg/CNPGCreateClusterDialog.test.tsx +113 -0
  66. package/src/components/cnpg/CNPGCreateClusterDialog.tsx +74 -0
  67. package/src/components/cnpg/CNPGDeclarations.test.ts +107 -0
  68. package/src/components/cnpg/CNPGDeclarations.tsx +236 -78
  69. package/src/components/cnpg/CNPGDetailPage.test.tsx +62 -0
  70. package/src/components/cnpg/CNPGDetailPage.tsx +159 -98
  71. package/src/components/cnpg/CNPGDrawerTrail.tsx +2 -2
  72. package/src/components/cnpg/CNPGInvestigationAction.test.tsx +30 -0
  73. package/src/components/cnpg/CNPGOperator.test.tsx +34 -0
  74. package/src/components/cnpg/CNPGOperator.tsx +78 -30
  75. package/src/components/cnpg/CNPGOperatorBanner.tsx +39 -0
  76. package/src/components/cnpg/CNPGOperatorConditions.test.tsx +45 -0
  77. package/src/components/cnpg/CNPGOperatorConditions.tsx +16 -0
  78. package/src/components/cnpg/CNPGOperatorConfig.test.tsx +27 -0
  79. package/src/components/cnpg/CNPGOperatorDiagnosis.tsx +247 -0
  80. package/src/components/cnpg/CNPGOverview.test.tsx +171 -0
  81. package/src/components/cnpg/CNPGOverview.tsx +238 -63
  82. package/src/components/cnpg/CNPGParametersInEffect.test.tsx +51 -0
  83. package/src/components/cnpg/CNPGParametersInEffect.tsx +120 -0
  84. package/src/components/cnpg/CNPGPerformance.prerequisite.test.tsx +48 -0
  85. package/src/components/cnpg/CNPGPerformance.test.tsx +73 -0
  86. package/src/components/cnpg/CNPGPerformance.tsx +477 -0
  87. package/src/components/cnpg/CNPGPooling.test.tsx +89 -0
  88. package/src/components/cnpg/CNPGPooling.tsx +97 -40
  89. package/src/components/cnpg/CNPGProtection.test.tsx +145 -0
  90. package/src/components/cnpg/CNPGProtection.tsx +230 -124
  91. package/src/components/cnpg/CNPGReplicationTab.test.tsx +87 -0
  92. package/src/components/cnpg/CNPGReplicationTab.tsx +212 -0
  93. package/src/components/cnpg/CNPGReplicationView.tsx +264 -0
  94. package/src/components/cnpg/CNPGStorage.test.tsx +140 -0
  95. package/src/components/cnpg/CNPGStorage.tsx +619 -0
  96. package/src/components/cnpg/CNPGSummaryHost.overview.test.tsx +44 -0
  97. package/src/components/cnpg/CNPGSummaryHost.test.tsx +23 -0
  98. package/src/components/cnpg/CNPGSummaryHost.tsx +228 -54
  99. package/src/components/cnpg/CNPGTrends.denied.test.tsx +33 -0
  100. package/src/components/cnpg/CNPGTrends.missing.test.tsx +69 -0
  101. package/src/components/cnpg/CNPGTrends.test.ts +38 -0
  102. package/src/components/cnpg/CNPGTrends.tsx +702 -0
  103. package/src/components/cnpg/CNPGView.tsx +34 -21
  104. package/src/components/cnpg/CNPGWorkspace.compatibility.test.tsx +90 -0
  105. package/src/components/cnpg/actions/CNPGClusterActions.dialog.test.tsx +79 -0
  106. package/src/components/cnpg/actions/CNPGClusterActions.test.tsx +142 -0
  107. package/src/components/cnpg/actions/CNPGClusterActions.tsx +853 -0
  108. package/src/components/cnpg/actions/CNPGConnectButton.read.test.tsx +40 -0
  109. package/src/components/cnpg/actions/CNPGConnectButton.test.ts +25 -0
  110. package/src/components/cnpg/actions/CNPGConnectButton.tsx +131 -0
  111. package/src/components/cnpg/actions/CNPGDestroyInstanceDialog.tsx +185 -0
  112. package/src/components/cnpg/actions/CNPGInstanceActions.test.tsx +38 -0
  113. package/src/components/cnpg/actions/CNPGInstanceActions.tsx +74 -0
  114. package/src/components/cnpg/actions/CNPGMaintenanceBanner.tsx +53 -0
  115. package/src/components/cnpg/actions/CNPGPoolerActions.test.tsx +35 -0
  116. package/src/components/cnpg/actions/CNPGPoolerActions.tsx +79 -0
  117. package/src/components/cnpg/actions/CNPGReportDialog.test.tsx +12 -0
  118. package/src/components/cnpg/actions/CNPGReportDialog.tsx +91 -0
  119. package/src/components/cnpg/actions/CNPGScheduleActions.run.test.tsx +58 -0
  120. package/src/components/cnpg/actions/CNPGScheduleActions.test.tsx +54 -0
  121. package/src/components/cnpg/actions/CNPGScheduleActions.tsx +208 -0
  122. package/src/components/cnpg/actions/actionModel.test.ts +111 -0
  123. package/src/components/cnpg/actions/actionModel.ts +127 -0
  124. package/src/components/cnpg/actions/renderCNPGHeaderActions.tsx +60 -0
  125. package/src/components/cnpg/actions/useCNPGWriteGuard.tsx +58 -0
  126. package/src/components/cnpg/actions/useOpenCNPGPsql.ts +22 -0
  127. package/src/components/cnpg/archivingRepair.test.ts +104 -0
  128. package/src/components/cnpg/archivingRepair.ts +145 -0
  129. package/src/components/cnpg/baseBackup.test.ts +36 -0
  130. package/src/components/cnpg/baseBackup.ts +36 -0
  131. package/src/components/cnpg/blocking.test.ts +59 -0
  132. package/src/components/cnpg/blocking.ts +131 -0
  133. package/src/components/cnpg/coverageText.test.ts +27 -0
  134. package/src/components/cnpg/creation/CNPGTargetFields.tsx +110 -0
  135. package/src/components/cnpg/creation/targetModel.test.ts +137 -0
  136. package/src/components/cnpg/creation/targetModel.ts +149 -0
  137. package/src/components/cnpg/fleetStatus.test.ts +46 -0
  138. package/src/components/cnpg/fleetStatus.ts +43 -0
  139. package/src/components/cnpg/formFields.ts +2 -0
  140. package/src/components/cnpg/grantText.test.tsx +11 -0
  141. package/src/components/cnpg/host.test.tsx +24 -0
  142. package/src/components/cnpg/host.tsx +101 -0
  143. package/src/components/cnpg/inspectModel.test.ts +120 -0
  144. package/src/components/cnpg/inspectModel.ts +140 -0
  145. package/src/components/cnpg/logicalSlots.test.ts +30 -0
  146. package/src/components/cnpg/logicalSlots.ts +29 -0
  147. package/src/components/cnpg/lsn.test.ts +40 -0
  148. package/src/components/cnpg/lsn.ts +43 -0
  149. package/src/components/cnpg/operations/CNPGFleetOperation.tsx +30 -0
  150. package/src/components/cnpg/operations/CNPGOperationTracker.test.tsx +247 -0
  151. package/src/components/cnpg/operations/CNPGOperationTracker.tsx +255 -0
  152. package/src/components/cnpg/operations/model.test.ts +510 -0
  153. package/src/components/cnpg/operations/model.ts +499 -0
  154. package/src/components/cnpg/operations/presentation.test.ts +80 -0
  155. package/src/components/cnpg/operations/presentation.ts +84 -0
  156. package/src/components/cnpg/operations/store.ts +98 -0
  157. package/src/components/cnpg/operatorStatus.test.ts +134 -0
  158. package/src/components/cnpg/operatorStatus.ts +194 -0
  159. package/src/components/cnpg/paths.test.ts +54 -0
  160. package/src/components/cnpg/paths.ts +47 -4
  161. package/src/components/cnpg/protection/CNPGAttachArchiveDialog.tsx +185 -0
  162. package/src/components/cnpg/protection/CNPGCreateScheduleDialog.tsx +196 -0
  163. package/src/components/cnpg/protection/CNPGProtectionSetup.test.tsx +121 -0
  164. package/src/components/cnpg/protection/CNPGProtectionSetup.tsx +452 -0
  165. package/src/components/cnpg/protection/CNPGScheduleInput.tsx +115 -0
  166. package/src/components/cnpg/protection/CNPGScheduleRepairButton.tsx +120 -0
  167. package/src/components/cnpg/protection/scheduleModel.test.ts +19 -0
  168. package/src/components/cnpg/protection/scheduleModel.ts +16 -0
  169. package/src/components/cnpg/recovery/CNPGRecoveryPoint.tsx +220 -0
  170. package/src/components/cnpg/recovery/CNPGRestoreButton.test.tsx +29 -0
  171. package/src/components/cnpg/recovery/CNPGRestoreButton.tsx +26 -0
  172. package/src/components/cnpg/recovery/CNPGRestoreChecks.test.tsx +23 -0
  173. package/src/components/cnpg/recovery/CNPGRestoreChecks.tsx +153 -0
  174. package/src/components/cnpg/recovery/CNPGRestoreDialog.back.test.tsx +149 -0
  175. package/src/components/cnpg/recovery/CNPGRestoreDialog.test.tsx +46 -0
  176. package/src/components/cnpg/recovery/CNPGRestoreDialog.tsx +333 -0
  177. package/src/components/cnpg/recovery/CNPGRestoreProgress.tsx +182 -0
  178. package/src/components/cnpg/recovery/CNPGRestoreValidation.tsx +216 -0
  179. package/src/components/cnpg/recovery/restoreModel.test.ts +709 -0
  180. package/src/components/cnpg/recovery/restoreModel.ts +685 -0
  181. package/src/components/cnpg/recovery/restoreOperation.ts +56 -0
  182. package/src/components/cnpg/refresh.test.ts +38 -0
  183. package/src/components/cnpg/refresh.ts +17 -0
  184. package/src/components/cnpg/refreshNotice.test.tsx +29 -0
  185. package/src/components/cnpg/routes.test.ts +34 -3
  186. package/src/components/cnpg/routes.ts +60 -48
  187. package/src/components/cnpg/runtimeAssessment.ts +198 -0
  188. package/src/components/cnpg/runtimeModel.test.ts +175 -0
  189. package/src/components/cnpg/runtimeModel.ts +196 -0
  190. package/src/components/cnpg/runtimeParts.test.tsx +17 -0
  191. package/src/components/cnpg/runtimeParts.tsx +79 -0
  192. package/src/components/cnpg/shared.test.tsx +59 -0
  193. package/src/components/cnpg/shared.tsx +100 -182
  194. package/src/components/cnpg/storageModel.test.ts +107 -0
  195. package/src/components/cnpg/storageModel.ts +101 -0
  196. package/src/components/cnpg/trendSamples.test.ts +148 -0
  197. package/src/components/cnpg/trendSamples.ts +222 -0
  198. package/src/components/cnpg/useCNPGClusterAssessment.ts +30 -0
  199. package/src/components/cnpg/useCNPGKubectlContext.test.tsx +45 -0
  200. package/src/components/cnpg/useCNPGKubectlContext.ts +13 -0
  201. package/src/components/cnpg/useCNPGNavigate.test.tsx +58 -0
  202. package/src/components/cnpg/useCNPGNavigate.ts +21 -0
  203. package/src/components/cnpg/useCNPGPoolerLive.ts +46 -0
  204. package/src/components/cnpg/useCNPGScreenParams.ts +24 -0
  205. package/src/components/cnpg/useCNPGSidebarWorkspace.ts +66 -16
  206. package/src/components/cnpg/workspaceRoute.ts +23 -0
  207. package/src/components/cost/ApplicationCostTab.test.ts +7 -0
  208. package/src/components/cost/ApplicationCostTab.tsx +7 -7
  209. package/src/components/cost/CostView.test.ts +2 -1
  210. package/src/components/cost/CostView.tsx +0 -2
  211. package/src/components/cost/WorkloadCostTab.tsx +2 -4
  212. package/src/components/cost/errors.ts +3 -1
  213. package/src/components/cost/source.test.ts +10 -0
  214. package/src/components/cost/source.ts +5 -1
  215. package/src/components/diagnose/AgentSetupNotice.test.tsx +58 -0
  216. package/src/components/diagnose/AgentSetupNotice.tsx +142 -51
  217. package/src/components/diagnose/ApplyDialog.ack.test.tsx +47 -0
  218. package/src/components/diagnose/ApplyDialog.test.tsx +59 -6
  219. package/src/components/diagnose/ApplyDialog.tsx +26 -28
  220. package/src/components/diagnose/DiagnoseContext.test.ts +43 -0
  221. package/src/components/diagnose/DiagnoseContext.tsx +136 -54
  222. package/src/components/diagnose/DiagnoseSurface.tsx +16 -3
  223. package/src/components/diagnose/InvestigationView.tsx +39 -2
  224. package/src/components/diagnose/LocalDiagnoseAction.tsx +40 -20
  225. package/src/components/diagnose/agentCatalog.ts +2 -2
  226. package/src/components/diagnose/diagnoseEvidenceTypes.ts +2 -0
  227. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +8 -0
  228. package/src/components/diagnose/investigationEvidence/projection.test.ts +16 -0
  229. package/src/components/dock/BottomDock.tsx +2 -0
  230. package/src/components/dock/TerminalTab.test.tsx +17 -0
  231. package/src/components/dock/TerminalTab.tsx +7 -3
  232. package/src/components/dock/TrafficFlowListTab.tsx +27 -6
  233. package/src/components/execution/BatchExecutionView.tsx +11 -11
  234. package/src/components/gitops/ArgoResourceDiffLoader.tsx +4 -2
  235. package/src/components/gitops/GitOpsView.tsx +16 -23
  236. package/src/components/helm/ChartBrowser.tsx +3 -3
  237. package/src/components/helm/HelmCompareRoute.test.tsx +59 -0
  238. package/src/components/helm/HelmCompareRoute.tsx +57 -26
  239. package/src/components/helm/HelmReleaseDrawer.test.ts +55 -3
  240. package/src/components/helm/HelmReleaseDrawer.tsx +50 -12
  241. package/src/components/helm/RevisionHistory.tsx +3 -2
  242. package/src/components/helm/TrackChartSourceDialog.render.test.tsx +120 -0
  243. package/src/components/helm/TrackChartSourceDialog.test.ts +50 -0
  244. package/src/components/helm/TrackChartSourceDialog.tsx +153 -176
  245. package/src/components/helm/ValuesViewer.test.tsx +301 -0
  246. package/src/components/helm/ValuesViewer.tsx +169 -78
  247. package/src/components/home/ActivitySummary.tsx +2 -1
  248. package/src/components/home/ClusterHealthCard.tsx +7 -0
  249. package/src/components/home/HomeView.tsx +20 -9
  250. package/src/components/home/ProblemsPanel.test.tsx +81 -0
  251. package/src/components/home/TopologyPreview.tsx +2 -2
  252. package/src/components/home/mcpToolCatalog.ts +1 -1
  253. package/src/components/issues/IssuesPane.render.test.tsx +122 -0
  254. package/src/components/issues/IssuesPane.test.ts +65 -1
  255. package/src/components/issues/IssuesPane.tsx +188 -25
  256. package/src/components/issues/uncheckedSources.ts +21 -0
  257. package/src/components/resource/PVCUsageBar.render.test.tsx +32 -0
  258. package/src/components/resource/PVCUsageBar.test.tsx +24 -0
  259. package/src/components/resource/PVCUsageBar.tsx +15 -6
  260. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +3 -1
  261. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
  262. package/src/components/resources/CompositeRenderer.tsx +4 -4
  263. package/src/components/resources/ResourceDetailDrawer.tsx +2 -2
  264. package/src/components/resources/ResourcesView.tsx +26 -12
  265. package/src/components/resources/renderers/KyvernoPolicyQueued.tsx +2 -1
  266. package/src/components/resources/renderers/NodeRenderer.tsx +7 -1
  267. package/src/components/resources/renderers/ServiceRenderer.test.tsx +81 -0
  268. package/src/components/resources/renderers/ServiceRenderer.tsx +19 -14
  269. package/src/components/settings/LocalIntegrationStatus.test.tsx +52 -0
  270. package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
  271. package/src/components/settings/SettingsDialog.tsx +150 -34
  272. package/src/components/shared/LargeClusterNamespacePicker.tsx +11 -5
  273. package/src/components/timeline/TimelineList.tsx +25 -24
  274. package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
  275. package/src/components/timeline/TimelineLoadError.tsx +96 -0
  276. package/src/components/timeline/TimelineRetentionNote.test.tsx +49 -0
  277. package/src/components/timeline/TimelineRetentionNote.tsx +20 -0
  278. package/src/components/timeline/TimelineView.tsx +25 -45
  279. package/src/components/timeline/ViewModeToggle.tsx +20 -0
  280. package/src/components/timeline/useStickyError.test.tsx +47 -0
  281. package/src/components/timeline/useStickyError.ts +15 -0
  282. package/src/components/traffic/TrafficConnectionsTable.tsx +144 -0
  283. package/src/components/traffic/TrafficFilterSidebar.tsx +22 -1
  284. package/src/components/traffic/TrafficFlowListContext.tsx +12 -4
  285. package/src/components/traffic/TrafficFocusSearch.tsx +172 -0
  286. package/src/components/traffic/TrafficGraph.tsx +109 -26
  287. package/src/components/traffic/TrafficGraphTooLarge.tsx +150 -0
  288. package/src/components/traffic/TrafficView.tsx +582 -149
  289. package/src/components/traffic/trafficFilters.test.ts +398 -1
  290. package/src/components/traffic/trafficFilters.ts +408 -0
  291. package/src/components/ui/DiagnosticsOverlay.tsx +34 -2
  292. package/src/components/ui/Markdown.test.tsx +65 -0
  293. package/src/components/ui/Markdown.tsx +144 -127
  294. package/src/components/whats-new/releaseNotes.ts +63 -1
  295. package/src/components/workload/WorkloadView.test.ts +11 -1
  296. package/src/components/workload/WorkloadView.tsx +162 -529
  297. package/src/components/workspace/layout.test.tsx +29 -0
  298. package/src/components/workspace/layout.tsx +259 -0
  299. package/src/components/workspace/table.ts +7 -0
  300. package/src/context/TimelineCustomization.tsx +15 -0
  301. package/src/contexts/CapabilitiesContext.tsx +1 -1
  302. package/src/hooks/useGitOpsWriteGuard.test.tsx +457 -0
  303. package/src/hooks/useGitOpsWriteGuard.ts +193 -0
  304. package/src/hooks/useResolvedGitOpsOwner.ts +256 -0
  305. package/src/integrations/resourceHost.test.tsx +55 -0
  306. package/src/integrations/resourceHost.ts +99 -0
  307. package/src/integrations/resourceHosts.test.tsx +141 -0
  308. package/src/integrations/resourceHosts.tsx +119 -0
  309. package/src/integrations/workspaceRoute.ts +20 -0
  310. package/src/integrations/workspaceRoutes.test.ts +40 -0
  311. package/src/integrations/workspaceRoutes.ts +22 -0
  312. package/src/integrations/workspaceScreens.tsx +14 -0
  313. package/src/utils/drawer-trail.ts +35 -0
  314. package/src/utils/kindFilterStore.test.ts +147 -0
  315. package/src/utils/kindFilterStore.ts +91 -0
  316. package/src/utils/navigation.test.ts +13 -1
  317. package/src/utils/navigation.ts +39 -2
  318. package/src/utils/page-links.ts +21 -0
  319. package/src/utils/tabStorage.ts +40 -0
  320. package/src/utils/viewMemory.test.ts +78 -0
  321. package/src/utils/viewMemory.ts +54 -0
@@ -0,0 +1,216 @@
1
+ import { useMemo, useState } from 'react'
2
+ import { useLocation, useSearchParams } from 'react-router-dom'
3
+ import { ActionConfirmDialog, Badge, cnpgRecoveryMatchesCluster, formatAge, isApiGroup } from '@skyhook-io/k8s-ui'
4
+ import { useCNPGClusterCapabilities, useCNPGWorkspace } from '../../../api/cnpg'
5
+ import { useRecordCNPGRestoreValidation, type CNPGRecoveryResponse } from '../../../api/cnpg-recovery'
6
+ import { actionDialogResult } from '../../../api/actions'
7
+ import { useCNPGWriteGuard } from '../actions/useCNPGWriteGuard'
8
+ import { useRestoreObservation, describeRecoverySource } from './CNPGRestoreProgress'
9
+ import { CNPGRestoreChecks } from './CNPGRestoreChecks'
10
+ import { RESTORE_VALIDATION_ANNOTATION, cnpgRestoredPrimaryUp, formatLocal, formatUTC, targetIsoFrom } from './restoreModel'
11
+
12
+ const CHECKLIST = [
13
+ 'Confirm the databases and roles Radar lists are the ones you expect',
14
+ 'Compare row counts of the tables you care about with the source',
15
+ 'Check where recovery stopped, as Radar reads it, against the point you needed',
16
+ 'Run the application’s own smoke query or health check against it',
17
+ ]
18
+
19
+ function useSourceCluster(namespace: string, name: string, snapshot: CNPGRecoveryResponse | undefined) {
20
+ const workspace = useCNPGWorkspace([namespace], { enabled: !!snapshot?.recovery })
21
+ return useMemo(() => {
22
+ const rec = snapshot?.recovery
23
+ const objects = workspace.data?.objects
24
+ if (!rec || !objects) return null
25
+ const clusters = (objects.clusters ?? []).filter((c: any) => isApiGroup(c.apiVersion, 'postgresql.cnpg.io'))
26
+ const restored = clusters.find((c: any) => c.metadata?.namespace === namespace && c.metadata?.name === name && c.metadata?.uid === snapshot.cluster.uid)
27
+ const source = restored ? clusters.find((c: any) => cnpgRecoveryMatchesCluster(restored, c, objects.backups ?? [])) : null
28
+ return source ? { namespace, name: source.metadata.name as string } : null
29
+ }, [snapshot, workspace.data, namespace, name])
30
+ }
31
+
32
+ /**
33
+ * Protection → Restore validation on a Cluster bootstrapped from backups. The
34
+ * record is a person's note of what they checked, stored on this Cluster as
35
+ * an annotation; Radar never turns it into a pass.
36
+ */
37
+ export function CNPGRestoreValidation({ namespace, name }: { namespace: string; name: string }) {
38
+ const { observation, snapshot } = useRestoreObservation(namespace, name)
39
+ const source = useSourceCluster(namespace, name, snapshot)
40
+ const [searchParams, setSearchParams] = useSearchParams()
41
+ const location = useLocation()
42
+ // ?validate=1 is the restore checklist's "Record what you checked" link.
43
+ const [open, setOpenState] = useState(() => searchParams.get('validate') === '1')
44
+ const setOpen = (next: boolean) => {
45
+ setOpenState(next)
46
+ if (!next && searchParams.has('validate')) {
47
+ const params = new URLSearchParams(searchParams)
48
+ params.delete('validate')
49
+ setSearchParams(params, { replace: true, state: location.state })
50
+ }
51
+ }
52
+ if (!snapshot?.recovery) return null
53
+ const note = snapshot.validation
54
+ const restoreDone = observation?.state === 'completed'
55
+
56
+ return (
57
+ <section className="mx-5 mt-3 overflow-hidden rounded-xl border border-theme-border bg-theme-surface shadow-theme-sm xl:mx-7">
58
+ <div className="flex flex-wrap items-center gap-2 border-b border-theme-border px-4 py-2.5">
59
+ <h2 className="text-sm font-semibold text-theme-text-primary">Restore validation</h2>
60
+ {note ? <Badge severity="neutral" size="sm">Recorded</Badge> : <Badge severity="neutral" size="sm">Not recorded</Badge>}
61
+ <span className="text-xs text-theme-text-tertiary">This cluster was restored from {describeRecoverySource(snapshot.recovery)}</span>
62
+ <div className="ml-auto">
63
+ <button type="button" onClick={() => setOpen(true)} className="btn-brand px-3 py-1.5 text-xs font-medium">
64
+ {note ? 'Record a new note…' : 'Record validation…'}
65
+ </button>
66
+ </div>
67
+ </div>
68
+ {(restoreDone || cnpgRestoredPrimaryUp(snapshot)) && <CNPGRestoreChecks namespace={namespace} name={name} />}
69
+ <div className="px-4 py-3 text-sm">
70
+ {note ? (
71
+ <dl className="grid grid-cols-[9rem_minmax(0,1fr)] gap-x-4 gap-y-1.5">
72
+ <dt className="text-theme-text-tertiary">Recorded</dt>
73
+ <dd className="text-theme-text-primary">
74
+ by {note.recordedBy || 'a user Radar could not identify (authentication is off)'} · {formatUTC(note.recordedAt)} ({formatAge(note.recordedAt)} ago)
75
+ </dd>
76
+ <dt className="text-theme-text-tertiary">What was checked</dt>
77
+ <dd className="whitespace-pre-wrap break-words text-theme-text-primary">{note.checked}</dd>
78
+ {note.targetTime && (
79
+ <>
80
+ <dt className="text-theme-text-tertiary">Recovery target</dt>
81
+ <dd className="text-theme-text-primary">{formatUTC(note.targetTime)}</dd>
82
+ </>
83
+ )}
84
+ <dt className="text-theme-text-tertiary">Source cluster</dt>
85
+ <dd className="text-theme-text-primary">
86
+ {note.source ? (
87
+ <>
88
+ {note.source.namespace}/{note.source.name}{' '}
89
+ <span className="font-mono text-[11px] text-theme-text-tertiary">{note.source.uid ? `uid ${note.source.uid}` : 'UID not read'}</span>
90
+ </>
91
+ ) : (
92
+ <span className="text-theme-text-tertiary">Not named</span>
93
+ )}
94
+ </dd>
95
+ <dt className="text-theme-text-tertiary">Restored cluster</dt>
96
+ <dd className="font-mono text-[11px] text-theme-text-tertiary">uid {note.target.uid}</dd>
97
+ </dl>
98
+ ) : (
99
+ <p className="text-theme-text-secondary">
100
+ {snapshot.validationError
101
+ ? `The recorded note cannot be read: ${snapshot.validationError}.`
102
+ : 'Nobody has recorded checking this restore. A restore that came up is one piece of evidence; what someone verified inside it is another.'}
103
+ </p>
104
+ )}
105
+ <p className="mt-2 text-[11px] text-theme-text-tertiary">
106
+ Stored on this Cluster as the {RESTORE_VALIDATION_ANNOTATION} annotation. It is a note, not a test Radar ran, so it never shows as passed.
107
+ </p>
108
+ </div>
109
+ {open && (
110
+ <RecordDialog
111
+ namespace={namespace}
112
+ name={name}
113
+ source={source}
114
+ defaultTarget={typeof snapshot.recovery.target?.targetTime === 'string' ? snapshot.recovery.target.targetTime : undefined}
115
+ restoreDone={restoreDone}
116
+ onClose={() => setOpen(false)}
117
+ />
118
+ )}
119
+ </section>
120
+ )
121
+ }
122
+
123
+ function toLocalInput(iso: string | undefined): string {
124
+ if (!iso) return ''
125
+ const d = new Date(iso)
126
+ if (Number.isNaN(d.getTime())) return ''
127
+ return d.toISOString().slice(0, 19)
128
+ }
129
+
130
+ function RecordDialog({
131
+ namespace,
132
+ name,
133
+ source,
134
+ defaultTarget,
135
+ restoreDone,
136
+ onClose,
137
+ }: {
138
+ namespace: string
139
+ name: string
140
+ source: { namespace: string; name: string } | null
141
+ defaultTarget?: string
142
+ restoreDone: boolean
143
+ onClose: () => void
144
+ }) {
145
+ const caps = useCNPGClusterCapabilities(namespace, name)
146
+ const mutation = useRecordCNPGRestoreValidation(namespace, name, caps.data?.context)
147
+ const guard = useCNPGWriteGuard({ namespace, name, scope: { kind: 'metadata', paths: [`metadata.annotations["${RESTORE_VALIDATION_ANNOTATION}"]`] } })
148
+ const [checked, setChecked] = useState('')
149
+ const [target, setTarget] = useState(toLocalInput(defaultTarget))
150
+ const targetIso = target ? targetIsoFrom(target, 'utc') : null
151
+ // Reload is gated on the same grant (patch clusters), so its permission answers for this write.
152
+ const patchCap = caps.data?.actions?.reload
153
+
154
+ return (
155
+ <ActionConfirmDialog
156
+ open
157
+ onClose={onClose}
158
+ onConfirm={() => {
159
+ if (!caps.data) return
160
+ mutation.mutate(
161
+ {
162
+ request: {
163
+ reviewedContext: caps.data.context,
164
+ uid: caps.data.uid,
165
+ params: { checked: checked.trim(), ...(targetIso ? { targetTime: targetIso } : {}), ...(source ? { source } : {}) },
166
+ },
167
+ },
168
+ { onSuccess: onClose },
169
+ )
170
+ }}
171
+ title="Record restore validation"
172
+ subject={{ kind: 'Cluster', namespace, name }}
173
+ context={caps.data?.context}
174
+ effect="Writes your note, your user name and the time onto this Cluster as an annotation. It records that you checked; it does not claim the restore passed."
175
+ confirmLabel="Record note"
176
+ guard={guard.node}
177
+ guardSatisfied={guard.satisfied}
178
+ isLoading={mutation.isPending}
179
+ result={actionDialogResult(mutation.outcomeError ?? mutation.error)}
180
+ writes={[{ summary: `patch Cluster ${namespace}/${name} metadata`, detail: `metadata.annotations["${RESTORE_VALIDATION_ANNOTATION}"] = {"checked": …, "recordedBy": <you>, "recordedAt": <now>, "source": …, "target": …}` }]}
181
+ warnings={restoreDone ? [] : ['The restore has not completed yet; record what you checked once it has.']}
182
+ disabledReason={caps.data && patchCap?.permission === 'denied' ? `Recording needs patch clusters in ${namespace}` : undefined}
183
+ incompleteReason={
184
+ !caps.data ? 'Loading…' : !checked.trim() ? 'Describe what you checked.' : target && !targetIso ? 'The recovery target is not a valid time.' : undefined
185
+ }
186
+ >
187
+ <div className="space-y-3">
188
+ <div>
189
+ <div className="mb-1 text-xs text-theme-text-secondary">Things worth checking</div>
190
+ <ul className="list-disc space-y-0.5 pl-5 text-xs text-theme-text-tertiary">
191
+ {CHECKLIST.map((c) => <li key={c}>{c}</li>)}
192
+ </ul>
193
+ </div>
194
+ <label className="block">
195
+ <span className="mb-1 block text-xs text-theme-text-secondary">What you checked, and what you found</span>
196
+ <textarea
197
+ value={checked}
198
+ onChange={(e) => setChecked(e.target.value)}
199
+ maxLength={2000}
200
+ rows={5}
201
+ className="w-full rounded-lg border border-theme-border bg-theme-base px-2 py-1.5 text-sm text-theme-text-primary"
202
+ placeholder="e.g. orders: 1,204,332 rows (source 1,204,340 at target); newest order 09:59:58 UTC"
203
+ />
204
+ </label>
205
+ <label className="block">
206
+ <span className="mb-1 block text-xs text-theme-text-secondary">Recovery target (UTC, optional)</span>
207
+ <input type="datetime-local" step={1} value={target} onChange={(e) => setTarget(e.target.value)} className="rounded-lg border border-theme-border bg-theme-base px-2 py-1 text-sm text-theme-text-primary" />
208
+ {targetIso && <span className="ml-2 text-[11px] text-theme-text-tertiary">{formatLocal(targetIso)}</span>}
209
+ </label>
210
+ <div className="text-xs text-theme-text-tertiary">
211
+ Source cluster: {source ? `${source.namespace}/${source.name} (its UID is read and recorded by the server)` : 'no live source Cluster could be attributed to this recovery; the note records none'}
212
+ </div>
213
+ </div>
214
+ </ActionConfirmDialog>
215
+ )
216
+ }