@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,452 @@
1
+ import { useEffect, useId, useState, type ReactNode } from 'react'
2
+ import { X } from 'lucide-react'
3
+ import { useLocation, useSearchParams } from 'react-router-dom'
4
+ import {
5
+ AlertBanner,
6
+ DialogPortal,
7
+ Badge,
8
+ FactGrid,
9
+ FactRow,
10
+ FactValue,
11
+ getCNPGClusterBarmanPlugin,
12
+ cnpgBackupDeclaration,
13
+ cnpgScheduleDestinationBlocker,
14
+ coverageReadable,
15
+ getRadarUpgradeRequirement,
16
+ RadarUpgradeNote,
17
+ type CNPGFleetRow,
18
+ } from '@skyhook-io/k8s-ui'
19
+ import { useCNPGClusterCapabilities, useCNPGOperator } from '../../../api/cnpg'
20
+ import { useRadarFeature } from '../../../api/client'
21
+ import { radarUpgradeRequirement } from '../../../api/radarFeatures'
22
+ import { useConnection } from '../../../context/ConnectionContext'
23
+ import { useCNPGFleet } from '../useCNPGSidebarWorkspace'
24
+ import { CNPGAttachArchiveDialog } from './CNPGAttachArchiveDialog'
25
+ import { CNPGCreateScheduleDialog } from './CNPGCreateScheduleDialog'
26
+ import { CNPGScheduleRepairDialog } from './CNPGScheduleRepairButton'
27
+ import { ClusterActionDialog } from '../actions/CNPGClusterActions'
28
+ import type { SelectedResource } from '../../../types'
29
+ import { cnpgResource } from '../shared'
30
+
31
+ export function CNPGProtectionSetup({
32
+ namespace,
33
+ name,
34
+ onInspect,
35
+ onOpenYaml,
36
+ onOpenOperator,
37
+ onOpenArchivingRepair,
38
+ }: {
39
+ namespace: string
40
+ name: string
41
+ onInspect: (resource: SelectedResource) => void
42
+ onOpenYaml?: () => void
43
+ onOpenOperator?: () => void
44
+ onOpenArchivingRepair?: () => void
45
+ }) {
46
+ const [params, setParams] = useSearchParams()
47
+ const location = useLocation()
48
+ const feature = useRadarFeature('cnpgProtectionSetup')
49
+ const [open, setOpen] = useState(() => params.get('protectionSetup') === '1')
50
+ const requested = params.get('protectionSetup') === '1'
51
+ useEffect(() => {
52
+ if (requested) setOpen(true)
53
+ }, [requested])
54
+ const close = () => {
55
+ setOpen(false)
56
+ if (params.has('protectionSetup')) {
57
+ const next = new URLSearchParams(params)
58
+ next.delete('protectionSetup')
59
+ setParams(next, { replace: true, state: location.state })
60
+ }
61
+ }
62
+ return (
63
+ <>
64
+ <button type="button" onClick={() => setOpen(true)} className="btn-brand px-3 py-1.5 text-xs">
65
+ Set up backups…
66
+ </button>
67
+ {feature.support === 'unsupported' ? (
68
+ <DialogPortal
69
+ open={open}
70
+ onClose={close}
71
+ ariaLabel="Backup setup needs a newer Radar"
72
+ className="w-full max-w-lg p-5"
73
+ >
74
+ <RadarUpgradeNote requirement={radarUpgradeRequirement('cnpgProtectionSetup', {})} />
75
+ <button type="button" onClick={close} className="btn-secondary mt-4 px-3 py-1.5 text-sm">
76
+ Close
77
+ </button>
78
+ </DialogPortal>
79
+ ) : (
80
+ open && (
81
+ <ProtectionGuide
82
+ namespace={namespace}
83
+ name={name}
84
+ onClose={close}
85
+ onInspect={onInspect}
86
+ onOpenYaml={onOpenYaml}
87
+ onOpenOperator={onOpenOperator}
88
+ onOpenArchivingRepair={onOpenArchivingRepair}
89
+ />
90
+ )
91
+ )}
92
+ </>
93
+ )
94
+ }
95
+
96
+ function Milestone({
97
+ number,
98
+ title,
99
+ state,
100
+ children,
101
+ }: {
102
+ number: number
103
+ title: string
104
+ state: string
105
+ children: ReactNode
106
+ }) {
107
+ return (
108
+ <section className="space-y-2 rounded-lg border border-theme-border bg-theme-surface p-3">
109
+ <div className="flex flex-wrap items-center gap-2">
110
+ <h4 className="font-medium">
111
+ {number}. {title}
112
+ </h4>
113
+ <Badge severity="neutral" size="sm">
114
+ {state}
115
+ </Badge>
116
+ </div>
117
+ {children}
118
+ </section>
119
+ )
120
+ }
121
+
122
+ function ProtectionGuide({
123
+ namespace,
124
+ name,
125
+ onClose,
126
+ onInspect,
127
+ onOpenYaml,
128
+ onOpenOperator,
129
+ onOpenArchivingRepair,
130
+ }: {
131
+ namespace: string
132
+ name: string
133
+ onClose: () => void
134
+ onInspect: (resource: SelectedResource) => void
135
+ onOpenYaml?: () => void
136
+ onOpenOperator?: () => void
137
+ onOpenArchivingRepair?: () => void
138
+ }) {
139
+ const titleId = useId()
140
+ const { connection } = useConnection()
141
+ const [context] = useState(connection.context)
142
+ const { query, fleet } = useCNPGFleet([namespace])
143
+ const operator = useCNPGOperator()
144
+ const caps = useCNPGClusterCapabilities(namespace, name)
145
+ const [step, setStep] = useState<'attach' | 'schedule' | 'backup' | null>(null)
146
+ const [repair, setRepair] = useState<string | null>(null)
147
+ const data = query.data
148
+ const row: CNPGFleetRow | undefined = fleet?.rows.find((r) => r.namespace === namespace && r.name === name)
149
+ const cluster = row?.cluster
150
+ const plugin = cluster ? getCNPGClusterBarmanPlugin(cluster) : null
151
+ const attached = !!plugin?.barmanObjectName && plugin.isWALArchiver
152
+ const declaration = cluster ? cnpgBackupDeclaration(cluster) : null
153
+ const unsupported =
154
+ !!declaration &&
155
+ (declaration.inTreeConfigured ||
156
+ declaration.snapshotsConfigured ||
157
+ declaration.plugins.some((p) => p.enabled && p.isWALArchiver && p.name !== 'barman-cloud.cloudnative-pg.io'))
158
+ const pluginVisible = operator.data?.components.some(
159
+ (component) => component.pluginName === 'barman-cloud.cloudnative-pg.io',
160
+ )
161
+ const pluginKnownAbsent = !pluginVisible && operator.data?.coverage.services.state === 'full'
162
+ const stores = (data?.objects.objectStores ?? []).filter((store) => store.metadata.namespace === namespace)
163
+ const schedules = (data?.objects.scheduledBackups ?? []).filter(
164
+ (schedule) => schedule.metadata.namespace === namespace && schedule.spec?.cluster?.name === name,
165
+ )
166
+ const scheduleCoverage = data?.coverage.scheduledBackups
167
+ const schedulesReadable = scheduleCoverage && coverageReadable(scheduleCoverage, namespace)
168
+ const matching = schedules.filter(
169
+ (schedule) =>
170
+ !cnpgScheduleDestinationBlocker(schedule, [cluster]) &&
171
+ schedule.spec?.method === 'plugin' &&
172
+ schedule.spec?.pluginConfiguration?.name === 'barman-cloud.cloudnative-pg.io',
173
+ )
174
+ const repairable = schedules.filter(
175
+ (schedule) =>
176
+ cnpgScheduleDestinationBlocker(schedule, [cluster]) &&
177
+ (schedule.spec?.method || 'barmanObjectStore') !== 'volumeSnapshot',
178
+ )
179
+ const changed = connection.context !== context
180
+ const upgrade = getRadarUpgradeRequirement(query.error)
181
+ if (step === 'attach' && cluster && !changed)
182
+ return (
183
+ <CNPGAttachArchiveDialog
184
+ namespace={namespace}
185
+ name={name}
186
+ uid={cluster.metadata.uid}
187
+ stores={stores}
188
+ current={
189
+ plugin?.barmanObjectName
190
+ ? { objectStore: plugin.barmanObjectName, serverName: plugin.serverName || name }
191
+ : undefined
192
+ }
193
+ onClose={() => setStep(null)}
194
+ />
195
+ )
196
+ if (step === 'schedule' && !changed)
197
+ return (
198
+ <CNPGCreateScheduleDialog
199
+ namespace={namespace}
200
+ cluster={name}
201
+ onClose={() => setStep(null)}
202
+ onCreated={() => void query.refetch()}
203
+ />
204
+ )
205
+ if (repair && !changed)
206
+ return <CNPGScheduleRepairDialog namespace={namespace} name={repair} onClose={() => setRepair(null)} />
207
+ if (step === 'backup' && caps.data && !changed)
208
+ return (
209
+ <ClusterActionDialog
210
+ kind="backup"
211
+ caps={caps.data}
212
+ namespace={namespace}
213
+ name={name}
214
+ onClose={() => setStep(null)}
215
+ />
216
+ )
217
+ return (
218
+ <DialogPortal open onClose={onClose} ariaLabelledBy={titleId} className="w-full max-w-4xl">
219
+ <div className="flex items-start gap-3 border-b border-theme-border p-4">
220
+ <div className="min-w-0 flex-1">
221
+ <h3 id={titleId} className="text-lg font-semibold text-theme-text-primary">
222
+ Set up backups for {name}
223
+ </h3>
224
+ <p className="mt-0.5 text-xs text-theme-text-tertiary">
225
+ Cluster{' '}
226
+ <span className="font-mono">
227
+ {namespace}/{name}
228
+ </span>{' '}
229
+ · context <span className="font-mono">{context}</span>
230
+ </p>
231
+ </div>
232
+ <button
233
+ type="button"
234
+ onClick={onClose}
235
+ aria-label="Close"
236
+ className="rounded p-1 text-theme-text-secondary hover:bg-theme-hover"
237
+ >
238
+ <X className="h-5 w-5" />
239
+ </button>
240
+ </div>
241
+ <div className="max-h-[70vh] space-y-4 overflow-y-auto p-4 text-sm text-theme-text-primary">
242
+ <p>
243
+ Connect existing storage, establish a matching schedule, then verify the results. Each change has its own
244
+ review; saving configuration alone does not prove protection.
245
+ </p>
246
+ {changed && (
247
+ <AlertBanner
248
+ variant="info"
249
+ title="Context changed"
250
+ message="Close this guide and start again in the current context."
251
+ />
252
+ )}
253
+ {!row ? (
254
+ <div>
255
+ {upgrade ? (
256
+ <RadarUpgradeNote requirement={upgrade} />
257
+ ) : (
258
+ <>
259
+ <p>
260
+ {query.error
261
+ ? query.error.message
262
+ : query.isLoading
263
+ ? 'Reading Cluster backup configuration…'
264
+ : 'This Cluster is not readable in the workspace. Check its identity and access before setup.'}
265
+ </p>
266
+ <button
267
+ type="button"
268
+ className="text-accent-text hover:underline"
269
+ onClick={() => void query.refetch()}
270
+ disabled={query.isFetching}
271
+ >
272
+ Retry
273
+ </button>
274
+ </>
275
+ )}
276
+ </div>
277
+ ) : (
278
+ <div className="space-y-3">
279
+ {row.protection.walArchiving.state === 'failing' && <AlertBanner variant="error" title="Repair WAL archiving first">
280
+ <p>Uploads are failing. Repair the archive connection before relying on a schedule or taking a new base backup.</p>
281
+ {onOpenArchivingRepair && <button type="button" className="mt-2 text-sm text-accent-text hover:underline" onClick={() => { onClose(); onOpenArchivingRepair() }}>Open archiving repair →</button>}
282
+ </AlertBanner>}
283
+ <Milestone number={1} title="Archive storage" state={row.protection.walArchiving.state === 'failing' ? 'Uploads failing' : attached ? 'Declared' : 'Not attached'}>
284
+ {attached ? (
285
+ <p className="text-sm text-theme-text-secondary">
286
+ ObjectStore <span className="font-mono">{plugin.barmanObjectName}</span> · archive server{' '}
287
+ <span className="font-mono">{plugin.serverName}</span>. Uploads still need verification.
288
+ </p>
289
+ ) : (
290
+ <>
291
+ <p className="text-sm text-theme-text-secondary">
292
+ Attach an existing ObjectStore in {namespace} and reserve a distinct archive identity for this
293
+ Cluster.
294
+ </p>
295
+ <button
296
+ type="button"
297
+ className="btn-secondary px-3 py-1.5 text-xs"
298
+ onClick={() => setStep('attach')}
299
+ disabled={unsupported || pluginKnownAbsent || changed}
300
+ >
301
+ Choose existing ObjectStore…
302
+ </button>
303
+ </>
304
+ )}
305
+ {unsupported && (
306
+ <p className="text-xs text-theme-text-secondary">
307
+ This configuration needs a backup-method migration. Review it in Cluster YAML; the guide preserves
308
+ existing in-tree, snapshot and other-plugin arrangements.
309
+ </p>
310
+ )}
311
+ {pluginKnownAbsent && (
312
+ <p className="text-xs text-theme-text-secondary">
313
+ No Barman plugin Service was found in the readable operator scope. Install it before attaching
314
+ storage.
315
+ </p>
316
+ )}
317
+ {!operator.data && (
318
+ <p className="text-xs text-theme-text-tertiary">
319
+ Plugin installation could not yet be established. Server review checks the declaration; it cannot
320
+ verify upload readiness.
321
+ </p>
322
+ )}
323
+ {onOpenOperator && (
324
+ <button
325
+ type="button"
326
+ className="text-xs text-accent-text hover:underline"
327
+ onClick={() => {
328
+ onClose()
329
+ onOpenOperator()
330
+ }}
331
+ >
332
+ Operator & plugin prerequisites →
333
+ </button>
334
+ )}
335
+ {onOpenYaml && (
336
+ <button
337
+ type="button"
338
+ className="ml-3 text-xs text-accent-text hover:underline"
339
+ onClick={() => {
340
+ onClose()
341
+ onOpenYaml()
342
+ }}
343
+ >
344
+ Cluster YAML →
345
+ </button>
346
+ )}
347
+ </Milestone>
348
+ <Milestone
349
+ number={2}
350
+ title="Backup schedule"
351
+ state={
352
+ !schedulesReadable ? 'Inventory incomplete' : matching.length ? 'Matching declaration' : 'Needs setup'
353
+ }
354
+ >
355
+ {matching.map((schedule) => (
356
+ <p key={schedule.metadata.name} className="text-sm text-theme-text-secondary">
357
+ <button
358
+ type="button"
359
+ className="text-accent-text hover:underline"
360
+ onClick={() => {
361
+ onClose()
362
+ onInspect(cnpgResource('ScheduledBackup', namespace, schedule.metadata.name))
363
+ }}
364
+ >
365
+ {schedule.metadata.name} →
366
+ </button>{' '}
367
+ · {schedule.spec.schedule}
368
+ {schedule.spec.suspend ? ' · suspended: open the schedule to resume it' : ''}
369
+ </p>
370
+ ))}
371
+ {repairable.map((schedule) => (
372
+ <div key={schedule.metadata.name} className="flex flex-wrap items-center gap-2 text-sm">
373
+ <span>{schedule.metadata.name} cannot use the declared destination.</span>
374
+ <button
375
+ type="button"
376
+ className="btn-secondary px-3 py-1.5 text-xs"
377
+ onClick={() => setRepair(schedule.metadata.name)}
378
+ disabled={!attached || changed}
379
+ >
380
+ Review method repair…
381
+ </button>
382
+ </div>
383
+ ))}
384
+ {!matching.length && (
385
+ <button
386
+ type="button"
387
+ className="btn-secondary px-3 py-1.5 text-xs"
388
+ onClick={() => setStep('schedule')}
389
+ disabled={!attached || changed}
390
+ >
391
+ Create matching schedule…
392
+ </button>
393
+ )}
394
+ {!attached && (
395
+ <p className="text-xs text-theme-text-tertiary">
396
+ Attach the Barman archive destination first. Existing schedules are left unchanged.
397
+ </p>
398
+ )}
399
+ {!schedulesReadable && (
400
+ <p className="text-xs text-theme-text-tertiary">
401
+ Schedules may exist outside the readable inventory. Check before creating another; strict creation
402
+ will not replace an existing object.
403
+ </p>
404
+ )}
405
+ </Milestone>
406
+ <Milestone number={3} title="Verify protection" state="Observed evidence">
407
+ <FactGrid>
408
+ <FactRow label="WAL archiving">
409
+ <FactValue fact={row.protection.walArchiving} />
410
+ </FactRow>
411
+ <FactRow label="Last successful backup">
412
+ <FactValue fact={row.protection.lastSuccessfulBackup} />
413
+ </FactRow>
414
+ </FactGrid>
415
+ <p className="text-xs text-theme-text-secondary">
416
+ Confirm uploads resumed, then complete a new base backup for this configuration. Older successful
417
+ backups do not prove a new attachment works. Restore validation is a separate task.
418
+ </p>
419
+ <button
420
+ type="button"
421
+ className="btn-secondary px-3 py-1.5 text-xs"
422
+ onClick={() => setStep('backup')}
423
+ disabled={!caps.data?.actions.backup.allowed || changed}
424
+ >
425
+ Back up now…
426
+ </button>
427
+ {!caps.data?.actions.backup.allowed && (
428
+ <p className="text-xs text-theme-text-tertiary">
429
+ {caps.data?.actions.backup.reason ||
430
+ (caps.error ? caps.error.message : 'Checking backup permission and Cluster state…')}
431
+ </p>
432
+ )}
433
+ <button
434
+ type="button"
435
+ className="ml-3 text-xs text-accent-text hover:underline"
436
+ onClick={() => void query.refetch()}
437
+ disabled={query.isFetching}
438
+ >
439
+ Refresh evidence
440
+ </button>
441
+ </Milestone>
442
+ </div>
443
+ )}
444
+ </div>
445
+ <div className="flex justify-end border-t border-theme-border p-4">
446
+ <button type="button" onClick={onClose} className="btn-secondary px-4 py-2 text-sm">
447
+ Close guide
448
+ </button>
449
+ </div>
450
+ </DialogPortal>
451
+ )
452
+ }
@@ -0,0 +1,115 @@
1
+ import { useId, useState } from 'react'
2
+ import { Input } from '@skyhook-io/k8s-ui'
3
+ import { scheduleExpression, simpleSchedule, type SimpleSchedule } from './scheduleModel'
4
+ import { CNPG_FORM_FIELD as FIELD } from '../formFields'
5
+
6
+ export function CNPGScheduleInput({ value, onChange }: { value: string; onChange: (value: string) => void }) {
7
+ const id = useId()
8
+ const simple = simpleSchedule(value)
9
+ const [advanced, setAdvanced] = useState(!simple)
10
+ const selected = advanced || !simple ? 'advanced' : simple.frequency
11
+ const set = (next: Partial<SimpleSchedule>) =>
12
+ onChange(scheduleExpression({ frequency: 'daily', time: '02:00', day: '1', ...simple, ...next }))
13
+ return (
14
+ <div className="space-y-2">
15
+ <div className="grid gap-3 sm:grid-cols-3">
16
+ <label className="block">
17
+ <span className="text-xs font-medium text-theme-text-secondary">Frequency</span>
18
+ <select
19
+ id={`${id}-frequency`}
20
+ aria-label="Backup frequency"
21
+ className={FIELD}
22
+ value={selected}
23
+ onChange={(event) => {
24
+ const frequency = event.target.value
25
+ setAdvanced(frequency === 'advanced')
26
+ if (frequency !== 'advanced')
27
+ set({
28
+ frequency: frequency as SimpleSchedule['frequency'],
29
+ day: frequency === 'weekly' ? '1' : simple?.frequency === 'monthly' ? simple.day : '1',
30
+ })
31
+ }}
32
+ >
33
+ <option value="daily">Daily</option>
34
+ <option value="weekly">Weekly</option>
35
+ <option value="monthly">Monthly</option>
36
+ <option value="advanced">Advanced cron</option>
37
+ </select>
38
+ </label>
39
+ {selected !== 'advanced' && (
40
+ <label className="block">
41
+ <span className="text-xs font-medium text-theme-text-secondary">Time · operator clock</span>
42
+ <Input
43
+ type="time"
44
+ className={FIELD}
45
+ value={simple?.time ?? '02:00'}
46
+ onChange={(event) => {
47
+ if (event.target.value) set({ time: event.target.value })
48
+ }}
49
+ />
50
+ </label>
51
+ )}
52
+ {selected === 'weekly' && (
53
+ <label className="block">
54
+ <span className="text-xs font-medium text-theme-text-secondary">Day</span>
55
+ <select
56
+ aria-label="Backup weekday"
57
+ className={FIELD}
58
+ value={simple?.day}
59
+ onChange={(event) => set({ day: event.target.value })}
60
+ >
61
+ {['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].map((day, index) => (
62
+ <option key={day} value={index}>
63
+ {day}
64
+ </option>
65
+ ))}
66
+ </select>
67
+ </label>
68
+ )}
69
+ {selected === 'monthly' && (
70
+ <label className="block">
71
+ <span className="text-xs font-medium text-theme-text-secondary">Day of month</span>
72
+ <select
73
+ aria-label="Backup day of month"
74
+ className={FIELD}
75
+ value={simple?.day}
76
+ onChange={(event) => set({ day: event.target.value })}
77
+ >
78
+ {Array.from({ length: 31 }, (_, index) => (
79
+ <option key={index} value={index + 1}>
80
+ {index + 1}
81
+ </option>
82
+ ))}
83
+ </select>
84
+ </label>
85
+ )}
86
+ </div>
87
+ {selected === 'advanced' ? (
88
+ <label className="block">
89
+ <span className="text-xs font-medium text-theme-text-secondary">
90
+ Schedule · seconds first, or a descriptor such as @daily
91
+ </span>
92
+ <Input
93
+ id="cnpg-schedule-input"
94
+ className={`${FIELD} font-mono`}
95
+ value={value}
96
+ onChange={(event) => onChange(event.target.value)}
97
+ spellCheck={false}
98
+ autoComplete="off"
99
+ />
100
+ </label>
101
+ ) : (
102
+ <div className="font-mono text-xs text-theme-text-tertiary">{value}</div>
103
+ )}
104
+ {selected === 'monthly' && Number(simple?.day) > 28 && (
105
+ <p className="text-xs text-theme-text-secondary">
106
+ Months without day {simple?.day} are skipped; no backup is scheduled for that month.
107
+ </p>
108
+ )}
109
+ <p className="text-xs text-theme-text-tertiary">
110
+ Times use the operator’s clock, not your browser’s. The server preview below states which clock is known or
111
+ assumed.
112
+ </p>
113
+ </div>
114
+ )
115
+ }
@@ -0,0 +1,120 @@
1
+ import { useState } from 'react'
2
+ import {
3
+ ActionConfirmDialog,
4
+ FactGrid,
5
+ FactRow,
6
+ RadarUpgradeNote,
7
+ getRadarUpgradeRequirement,
8
+ } from '@skyhook-io/k8s-ui'
9
+ import { useCNPGProtectionAction, useCNPGScheduleMethodPreview } from '../../../api/cnpg-protection'
10
+ import { useConnection } from '../../../context/ConnectionContext'
11
+ import { actionDialogResult } from '../../../api/actions'
12
+ import { useCNPGWriteGuard } from '../actions/useCNPGWriteGuard'
13
+ import { useToast } from '../../ui/Toast'
14
+
15
+ export function CNPGScheduleRepairButton({ namespace, name }: { namespace: string; name: string }) {
16
+ const [open, setOpen] = useState(false)
17
+ return (
18
+ <>
19
+ <button type="button" className="btn-secondary px-3 py-1.5 text-xs" onClick={() => setOpen(true)}>
20
+ Match {name} to the Barman plugin…
21
+ </button>
22
+ {open && <CNPGScheduleRepairDialog namespace={namespace} name={name} onClose={() => setOpen(false)} />}
23
+ </>
24
+ )
25
+ }
26
+
27
+ export function CNPGScheduleRepairDialog({
28
+ namespace,
29
+ name,
30
+ onClose,
31
+ }: {
32
+ namespace: string
33
+ name: string
34
+ onClose: () => void
35
+ }) {
36
+ const preview = useCNPGScheduleMethodPreview(namespace, name)
37
+ const mutation = useCNPGProtectionAction('scheduledbackups', namespace, name, preview.data?.context)
38
+ const { connection } = useConnection()
39
+ const [context] = useState(connection.context)
40
+ const { showSuccess } = useToast()
41
+ const guard = useCNPGWriteGuard({
42
+ namespace,
43
+ name,
44
+ targetKind: 'ScheduledBackup',
45
+ scope: { kind: 'spec', paths: ['spec.method', 'spec.pluginConfiguration.name'] },
46
+ })
47
+ const data = preview.data
48
+ const changed = connection.context !== context || (data && data.context !== context)
49
+ const upgrade = getRadarUpgradeRequirement(preview.error)
50
+ return (
51
+ <ActionConfirmDialog
52
+ open
53
+ onClose={onClose}
54
+ onConfirm={() => {
55
+ if (!data) return
56
+ mutation.mutate(
57
+ {
58
+ kind: 'scheduledbackups',
59
+ action: 'repairMethod',
60
+ request: { reviewedContext: data.context, uid: data.uid, facts: data.facts },
61
+ },
62
+ {
63
+ onSuccess: () => {
64
+ showSuccess(`${name} now uses the Cluster’s Barman plugin. Verify its next backup.`)
65
+ onClose()
66
+ },
67
+ },
68
+ )
69
+ }}
70
+ title="Repair backup schedule method"
71
+ subject={{ kind: 'ScheduledBackup', namespace, name }}
72
+ context={context}
73
+ effect="Use the Cluster’s existing Barman plugin for this schedule. Cluster target, timing, immediate/online choices and other settings are preserved."
74
+ disabledReason={
75
+ changed
76
+ ? 'The context changed. Close and review this repair again.'
77
+ : data?.unchanged
78
+ ? 'The schedule already matches the Barman plugin.'
79
+ : preview.error
80
+ ? preview.error.message
81
+ : undefined
82
+ }
83
+ incompleteReason={!data && !preview.error ? 'Checking the method and server dry-run…' : undefined}
84
+ guard={data ? guard.node : undefined}
85
+ guardSatisfied={!!data && guard.satisfied}
86
+ confirmLabel="Repair method"
87
+ isLoading={mutation.isPending}
88
+ result={actionDialogResult(mutation.outcomeError ?? mutation.error, { unknown: "An earlier write to this ScheduledBackup may have taken effect" })}
89
+ writes={
90
+ data
91
+ ? [
92
+ {
93
+ summary: `patch ScheduledBackup ${namespace}/${name}`,
94
+ detail: `spec.method = plugin; spec.pluginConfiguration.name = barman-cloud.cloudnative-pg.io`,
95
+ },
96
+ ]
97
+ : undefined
98
+ }
99
+ >
100
+ {upgrade ? (
101
+ <RadarUpgradeNote requirement={upgrade} />
102
+ ) : data ? (
103
+ <FactGrid>
104
+ <FactRow label="Cluster">{data.cluster}</FactRow>
105
+ <FactRow label="Current method">{data.previousMethod}</FactRow>
106
+ <FactRow label="After repair">plugin · barman-cloud.cloudnative-pg.io</FactRow>
107
+ </FactGrid>
108
+ ) : preview.error ? (
109
+ <button
110
+ type="button"
111
+ onClick={() => void preview.refetch()}
112
+ disabled={preview.isFetching}
113
+ className="text-accent-text hover:underline"
114
+ >
115
+ Retry review
116
+ </button>
117
+ ) : null}
118
+ </ActionConfirmDialog>
119
+ )
120
+ }