@skyhook-io/radar-app 1.17.0 → 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 (276) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/App.tsx +125 -87
  4. package/src/api/actions.errorCode.test.ts +40 -0
  5. package/src/api/actions.test.ts +48 -0
  6. package/src/api/actions.ts +79 -0
  7. package/src/api/apiResources.navigation.test.tsx +232 -0
  8. package/src/api/apiResources.ts +46 -3
  9. package/src/api/client.helmSource.test.ts +39 -0
  10. package/src/api/client.namespaceSwitch.hook.test.tsx +54 -0
  11. package/src/api/client.namespaceSwitch.test.ts +107 -0
  12. package/src/api/client.ts +266 -39
  13. package/src/api/cnpg-ha.live.test.ts +140 -0
  14. package/src/api/cnpg-ha.test.ts +78 -0
  15. package/src/api/cnpg-ha.ts +19 -0
  16. package/src/api/cnpg-history.ts +123 -0
  17. package/src/api/cnpg-inspect.ts +102 -0
  18. package/src/api/cnpg-protection.ts +112 -0
  19. package/src/api/cnpg-recovery.test.tsx +60 -0
  20. package/src/api/cnpg-recovery.ts +262 -0
  21. package/src/api/cnpg-sessions.ts +175 -0
  22. package/src/api/cnpg-storage.ts +138 -0
  23. package/src/api/cnpg.legacy-reads.test.ts +43 -0
  24. package/src/api/cnpg.pooler-runtime.test.ts +15 -0
  25. package/src/api/cnpg.ts +428 -10
  26. package/src/api/radarFeatures.render.test.tsx +47 -3
  27. package/src/api/radarFeatures.ts +4 -1
  28. package/src/api/reviewedActionMutation.test.tsx +164 -0
  29. package/src/api/reviewedActionMutation.ts +66 -0
  30. package/src/api/timelineSource.test.ts +9 -0
  31. package/src/api/timelineSource.ts +8 -0
  32. package/src/components/audit/AuditView.tsx +9 -0
  33. package/src/components/capacity/CapacityActivity.tsx +3 -31
  34. package/src/components/capacity/CapacityDemand.tsx +4 -36
  35. package/src/components/capacity/CapacityOverview.tsx +217 -56
  36. package/src/components/capacity/CapacityPoolDetail.tsx +6 -55
  37. package/src/components/capacity/CapacityView.test.tsx +323 -0
  38. package/src/components/capacity/ClusterSchedulingCard.tsx +2 -10
  39. package/src/components/capacity/certaintyGlyph.test.tsx +2 -9
  40. package/src/components/capacity/routes.ts +42 -0
  41. package/src/components/capacity/shared.tsx +11 -129
  42. package/src/components/capacity/workspaceRoute.ts +18 -0
  43. package/src/components/cnpg/CNPGActionHost.compatibility.test.tsx +30 -0
  44. package/src/components/cnpg/CNPGArchivingRepair.tsx +191 -0
  45. package/src/components/cnpg/CNPGBlockingSessions.test.tsx +50 -0
  46. package/src/components/cnpg/CNPGBlockingSessions.tsx +333 -0
  47. package/src/components/cnpg/CNPGClusterActivity.test.tsx +11 -0
  48. package/src/components/cnpg/CNPGClusterActivity.tsx +18 -9
  49. package/src/components/cnpg/CNPGClusterLogs.compatibility.test.tsx +130 -0
  50. package/src/components/cnpg/CNPGClusterLogs.empty.test.tsx +29 -0
  51. package/src/components/cnpg/CNPGClusterLogs.test.ts +9 -0
  52. package/src/components/cnpg/CNPGClusterLogs.tsx +81 -24
  53. package/src/components/cnpg/CNPGClusterTabs.test.tsx +152 -0
  54. package/src/components/cnpg/CNPGClusterTabs.tsx +187 -0
  55. package/src/components/cnpg/CNPGClustersKindView.tsx +46 -0
  56. package/src/components/cnpg/CNPGConfiguration.test.tsx +223 -0
  57. package/src/components/cnpg/CNPGConfiguration.tsx +190 -0
  58. package/src/components/cnpg/CNPGCreateClusterDialog.test.tsx +113 -0
  59. package/src/components/cnpg/CNPGCreateClusterDialog.tsx +74 -0
  60. package/src/components/cnpg/CNPGDeclarations.test.ts +107 -0
  61. package/src/components/cnpg/CNPGDeclarations.tsx +236 -78
  62. package/src/components/cnpg/CNPGDetailPage.test.tsx +62 -0
  63. package/src/components/cnpg/CNPGDetailPage.tsx +159 -98
  64. package/src/components/cnpg/CNPGDrawerTrail.tsx +2 -2
  65. package/src/components/cnpg/CNPGInvestigationAction.test.tsx +30 -0
  66. package/src/components/cnpg/CNPGOperator.test.tsx +34 -0
  67. package/src/components/cnpg/CNPGOperator.tsx +78 -30
  68. package/src/components/cnpg/CNPGOperatorBanner.tsx +39 -0
  69. package/src/components/cnpg/CNPGOperatorConditions.test.tsx +45 -0
  70. package/src/components/cnpg/CNPGOperatorConditions.tsx +16 -0
  71. package/src/components/cnpg/CNPGOperatorConfig.test.tsx +27 -0
  72. package/src/components/cnpg/CNPGOperatorDiagnosis.tsx +247 -0
  73. package/src/components/cnpg/CNPGOverview.test.tsx +171 -0
  74. package/src/components/cnpg/CNPGOverview.tsx +238 -63
  75. package/src/components/cnpg/CNPGParametersInEffect.test.tsx +51 -0
  76. package/src/components/cnpg/CNPGParametersInEffect.tsx +120 -0
  77. package/src/components/cnpg/CNPGPerformance.prerequisite.test.tsx +48 -0
  78. package/src/components/cnpg/CNPGPerformance.test.tsx +73 -0
  79. package/src/components/cnpg/CNPGPerformance.tsx +477 -0
  80. package/src/components/cnpg/CNPGPooling.test.tsx +89 -0
  81. package/src/components/cnpg/CNPGPooling.tsx +97 -40
  82. package/src/components/cnpg/CNPGProtection.test.tsx +145 -0
  83. package/src/components/cnpg/CNPGProtection.tsx +228 -122
  84. package/src/components/cnpg/CNPGReplicationTab.test.tsx +87 -0
  85. package/src/components/cnpg/CNPGReplicationTab.tsx +212 -0
  86. package/src/components/cnpg/CNPGReplicationView.tsx +264 -0
  87. package/src/components/cnpg/CNPGStorage.test.tsx +140 -0
  88. package/src/components/cnpg/CNPGStorage.tsx +619 -0
  89. package/src/components/cnpg/CNPGSummaryHost.overview.test.tsx +44 -0
  90. package/src/components/cnpg/CNPGSummaryHost.test.tsx +23 -0
  91. package/src/components/cnpg/CNPGSummaryHost.tsx +228 -54
  92. package/src/components/cnpg/CNPGTrends.denied.test.tsx +33 -0
  93. package/src/components/cnpg/CNPGTrends.missing.test.tsx +69 -0
  94. package/src/components/cnpg/CNPGTrends.test.ts +38 -0
  95. package/src/components/cnpg/CNPGTrends.tsx +702 -0
  96. package/src/components/cnpg/CNPGView.tsx +34 -21
  97. package/src/components/cnpg/CNPGWorkspace.compatibility.test.tsx +90 -0
  98. package/src/components/cnpg/actions/CNPGClusterActions.dialog.test.tsx +79 -0
  99. package/src/components/cnpg/actions/CNPGClusterActions.test.tsx +142 -0
  100. package/src/components/cnpg/actions/CNPGClusterActions.tsx +853 -0
  101. package/src/components/cnpg/actions/CNPGConnectButton.read.test.tsx +40 -0
  102. package/src/components/cnpg/actions/CNPGConnectButton.test.ts +25 -0
  103. package/src/components/cnpg/actions/CNPGConnectButton.tsx +131 -0
  104. package/src/components/cnpg/actions/CNPGDestroyInstanceDialog.tsx +185 -0
  105. package/src/components/cnpg/actions/CNPGInstanceActions.test.tsx +38 -0
  106. package/src/components/cnpg/actions/CNPGInstanceActions.tsx +74 -0
  107. package/src/components/cnpg/actions/CNPGMaintenanceBanner.tsx +53 -0
  108. package/src/components/cnpg/actions/CNPGPoolerActions.test.tsx +35 -0
  109. package/src/components/cnpg/actions/CNPGPoolerActions.tsx +79 -0
  110. package/src/components/cnpg/actions/CNPGReportDialog.test.tsx +12 -0
  111. package/src/components/cnpg/actions/CNPGReportDialog.tsx +91 -0
  112. package/src/components/cnpg/actions/CNPGScheduleActions.run.test.tsx +58 -0
  113. package/src/components/cnpg/actions/CNPGScheduleActions.test.tsx +54 -0
  114. package/src/components/cnpg/actions/CNPGScheduleActions.tsx +208 -0
  115. package/src/components/cnpg/actions/actionModel.test.ts +111 -0
  116. package/src/components/cnpg/actions/actionModel.ts +127 -0
  117. package/src/components/cnpg/actions/renderCNPGHeaderActions.tsx +60 -0
  118. package/src/components/cnpg/actions/useCNPGWriteGuard.tsx +58 -0
  119. package/src/components/cnpg/actions/useOpenCNPGPsql.ts +22 -0
  120. package/src/components/cnpg/archivingRepair.test.ts +104 -0
  121. package/src/components/cnpg/archivingRepair.ts +145 -0
  122. package/src/components/cnpg/baseBackup.test.ts +36 -0
  123. package/src/components/cnpg/baseBackup.ts +36 -0
  124. package/src/components/cnpg/blocking.test.ts +59 -0
  125. package/src/components/cnpg/blocking.ts +131 -0
  126. package/src/components/cnpg/coverageText.test.ts +27 -0
  127. package/src/components/cnpg/creation/CNPGTargetFields.tsx +110 -0
  128. package/src/components/cnpg/creation/targetModel.test.ts +137 -0
  129. package/src/components/cnpg/creation/targetModel.ts +149 -0
  130. package/src/components/cnpg/fleetStatus.test.ts +46 -0
  131. package/src/components/cnpg/fleetStatus.ts +43 -0
  132. package/src/components/cnpg/formFields.ts +2 -0
  133. package/src/components/cnpg/grantText.test.tsx +11 -0
  134. package/src/components/cnpg/host.test.tsx +24 -0
  135. package/src/components/cnpg/host.tsx +101 -0
  136. package/src/components/cnpg/inspectModel.test.ts +120 -0
  137. package/src/components/cnpg/inspectModel.ts +140 -0
  138. package/src/components/cnpg/logicalSlots.test.ts +30 -0
  139. package/src/components/cnpg/logicalSlots.ts +29 -0
  140. package/src/components/cnpg/lsn.test.ts +40 -0
  141. package/src/components/cnpg/lsn.ts +43 -0
  142. package/src/components/cnpg/operations/CNPGFleetOperation.tsx +30 -0
  143. package/src/components/cnpg/operations/CNPGOperationTracker.test.tsx +247 -0
  144. package/src/components/cnpg/operations/CNPGOperationTracker.tsx +255 -0
  145. package/src/components/cnpg/operations/model.test.ts +510 -0
  146. package/src/components/cnpg/operations/model.ts +499 -0
  147. package/src/components/cnpg/operations/presentation.test.ts +80 -0
  148. package/src/components/cnpg/operations/presentation.ts +84 -0
  149. package/src/components/cnpg/operations/store.ts +98 -0
  150. package/src/components/cnpg/operatorStatus.test.ts +134 -0
  151. package/src/components/cnpg/operatorStatus.ts +194 -0
  152. package/src/components/cnpg/paths.test.ts +54 -0
  153. package/src/components/cnpg/paths.ts +47 -4
  154. package/src/components/cnpg/protection/CNPGAttachArchiveDialog.tsx +185 -0
  155. package/src/components/cnpg/protection/CNPGCreateScheduleDialog.tsx +196 -0
  156. package/src/components/cnpg/protection/CNPGProtectionSetup.test.tsx +121 -0
  157. package/src/components/cnpg/protection/CNPGProtectionSetup.tsx +452 -0
  158. package/src/components/cnpg/protection/CNPGScheduleInput.tsx +115 -0
  159. package/src/components/cnpg/protection/CNPGScheduleRepairButton.tsx +120 -0
  160. package/src/components/cnpg/protection/scheduleModel.test.ts +19 -0
  161. package/src/components/cnpg/protection/scheduleModel.ts +16 -0
  162. package/src/components/cnpg/recovery/CNPGRecoveryPoint.tsx +220 -0
  163. package/src/components/cnpg/recovery/CNPGRestoreButton.test.tsx +29 -0
  164. package/src/components/cnpg/recovery/CNPGRestoreButton.tsx +26 -0
  165. package/src/components/cnpg/recovery/CNPGRestoreChecks.test.tsx +23 -0
  166. package/src/components/cnpg/recovery/CNPGRestoreChecks.tsx +153 -0
  167. package/src/components/cnpg/recovery/CNPGRestoreDialog.back.test.tsx +149 -0
  168. package/src/components/cnpg/recovery/CNPGRestoreDialog.test.tsx +46 -0
  169. package/src/components/cnpg/recovery/CNPGRestoreDialog.tsx +333 -0
  170. package/src/components/cnpg/recovery/CNPGRestoreProgress.tsx +182 -0
  171. package/src/components/cnpg/recovery/CNPGRestoreValidation.tsx +216 -0
  172. package/src/components/cnpg/recovery/restoreModel.test.ts +709 -0
  173. package/src/components/cnpg/recovery/restoreModel.ts +685 -0
  174. package/src/components/cnpg/recovery/restoreOperation.ts +56 -0
  175. package/src/components/cnpg/refresh.test.ts +38 -0
  176. package/src/components/cnpg/refresh.ts +17 -0
  177. package/src/components/cnpg/refreshNotice.test.tsx +29 -0
  178. package/src/components/cnpg/routes.test.ts +34 -3
  179. package/src/components/cnpg/routes.ts +60 -48
  180. package/src/components/cnpg/runtimeAssessment.ts +198 -0
  181. package/src/components/cnpg/runtimeModel.test.ts +175 -0
  182. package/src/components/cnpg/runtimeModel.ts +196 -0
  183. package/src/components/cnpg/runtimeParts.test.tsx +17 -0
  184. package/src/components/cnpg/runtimeParts.tsx +79 -0
  185. package/src/components/cnpg/shared.test.tsx +59 -0
  186. package/src/components/cnpg/shared.tsx +100 -182
  187. package/src/components/cnpg/storageModel.test.ts +107 -0
  188. package/src/components/cnpg/storageModel.ts +101 -0
  189. package/src/components/cnpg/trendSamples.test.ts +148 -0
  190. package/src/components/cnpg/trendSamples.ts +222 -0
  191. package/src/components/cnpg/useCNPGClusterAssessment.ts +30 -0
  192. package/src/components/cnpg/useCNPGKubectlContext.test.tsx +45 -0
  193. package/src/components/cnpg/useCNPGKubectlContext.ts +13 -0
  194. package/src/components/cnpg/useCNPGNavigate.test.tsx +58 -0
  195. package/src/components/cnpg/useCNPGNavigate.ts +21 -0
  196. package/src/components/cnpg/useCNPGPoolerLive.ts +46 -0
  197. package/src/components/cnpg/useCNPGScreenParams.ts +24 -0
  198. package/src/components/cnpg/useCNPGSidebarWorkspace.ts +66 -16
  199. package/src/components/cnpg/workspaceRoute.ts +23 -0
  200. package/src/components/cost/ApplicationCostTab.test.ts +7 -0
  201. package/src/components/cost/ApplicationCostTab.tsx +7 -7
  202. package/src/components/cost/CostView.test.ts +2 -1
  203. package/src/components/cost/CostView.tsx +0 -2
  204. package/src/components/cost/WorkloadCostTab.tsx +2 -4
  205. package/src/components/cost/errors.ts +3 -1
  206. package/src/components/cost/source.test.ts +10 -0
  207. package/src/components/cost/source.ts +5 -1
  208. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +8 -0
  209. package/src/components/diagnose/investigationEvidence/projection.test.ts +16 -0
  210. package/src/components/dock/BottomDock.tsx +2 -0
  211. package/src/components/dock/TerminalTab.test.tsx +17 -0
  212. package/src/components/dock/TerminalTab.tsx +7 -3
  213. package/src/components/gitops/ArgoResourceDiffLoader.tsx +4 -2
  214. package/src/components/gitops/GitOpsView.tsx +16 -23
  215. package/src/components/helm/HelmCompareRoute.test.tsx +59 -0
  216. package/src/components/helm/HelmCompareRoute.tsx +57 -26
  217. package/src/components/helm/HelmReleaseDrawer.test.ts +55 -3
  218. package/src/components/helm/HelmReleaseDrawer.tsx +50 -12
  219. package/src/components/helm/TrackChartSourceDialog.render.test.tsx +120 -0
  220. package/src/components/helm/TrackChartSourceDialog.test.ts +50 -0
  221. package/src/components/helm/TrackChartSourceDialog.tsx +153 -176
  222. package/src/components/helm/ValuesViewer.test.tsx +301 -0
  223. package/src/components/helm/ValuesViewer.tsx +169 -78
  224. package/src/components/home/ActivitySummary.tsx +2 -1
  225. package/src/components/home/HomeView.tsx +20 -9
  226. package/src/components/home/ProblemsPanel.test.tsx +81 -0
  227. package/src/components/home/TopologyPreview.tsx +2 -2
  228. package/src/components/issues/IssuesPane.render.test.tsx +122 -0
  229. package/src/components/issues/IssuesPane.test.ts +65 -1
  230. package/src/components/issues/IssuesPane.tsx +188 -25
  231. package/src/components/issues/uncheckedSources.ts +21 -0
  232. package/src/components/resource/PVCUsageBar.render.test.tsx +32 -0
  233. package/src/components/resource/PVCUsageBar.test.tsx +24 -0
  234. package/src/components/resource/PVCUsageBar.tsx +15 -6
  235. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +3 -1
  236. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
  237. package/src/components/resources/CompositeRenderer.tsx +4 -4
  238. package/src/components/resources/ResourceDetailDrawer.tsx +2 -2
  239. package/src/components/resources/ResourcesView.tsx +26 -12
  240. package/src/components/settings/LocalIntegrationStatus.test.tsx +52 -0
  241. package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
  242. package/src/components/settings/SettingsDialog.tsx +28 -15
  243. package/src/components/timeline/TimelineList.tsx +22 -24
  244. package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
  245. package/src/components/timeline/TimelineLoadError.tsx +96 -0
  246. package/src/components/timeline/TimelineView.tsx +18 -45
  247. package/src/components/timeline/ViewModeToggle.tsx +20 -0
  248. package/src/components/timeline/useStickyError.test.tsx +47 -0
  249. package/src/components/timeline/useStickyError.ts +15 -0
  250. package/src/components/ui/DiagnosticsOverlay.tsx +34 -2
  251. package/src/components/ui/Markdown.test.tsx +65 -0
  252. package/src/components/ui/Markdown.tsx +144 -127
  253. package/src/components/whats-new/releaseNotes.ts +63 -1
  254. package/src/components/workload/WorkloadView.test.ts +10 -1
  255. package/src/components/workload/WorkloadView.tsx +27 -239
  256. package/src/components/workspace/layout.test.tsx +29 -0
  257. package/src/components/workspace/layout.tsx +259 -0
  258. package/src/components/workspace/table.ts +7 -0
  259. package/src/contexts/CapabilitiesContext.tsx +1 -1
  260. package/src/integrations/resourceHost.test.tsx +55 -0
  261. package/src/integrations/resourceHost.ts +99 -0
  262. package/src/integrations/resourceHosts.test.tsx +141 -0
  263. package/src/integrations/resourceHosts.tsx +119 -0
  264. package/src/integrations/workspaceRoute.ts +20 -0
  265. package/src/integrations/workspaceRoutes.test.ts +40 -0
  266. package/src/integrations/workspaceRoutes.ts +22 -0
  267. package/src/integrations/workspaceScreens.tsx +14 -0
  268. package/src/utils/drawer-trail.ts +35 -0
  269. package/src/utils/kindFilterStore.test.ts +147 -0
  270. package/src/utils/kindFilterStore.ts +91 -0
  271. package/src/utils/navigation.test.ts +13 -1
  272. package/src/utils/navigation.ts +39 -2
  273. package/src/utils/page-links.ts +21 -0
  274. package/src/utils/tabStorage.ts +40 -0
  275. package/src/utils/viewMemory.test.ts +78 -0
  276. package/src/utils/viewMemory.ts +54 -0
@@ -0,0 +1,185 @@
1
+ import { useState } from 'react'
2
+ import {
3
+ ActionConfirmDialog,
4
+ FactGrid,
5
+ FactRow,
6
+ Input,
7
+ RadarUpgradeNote,
8
+ getRadarUpgradeRequirement,
9
+ } from '@skyhook-io/k8s-ui'
10
+ import { useCNPGArchivingPreview, useCNPGProtectionAction } from '../../../api/cnpg-protection'
11
+ import { useConnection } from '../../../context/ConnectionContext'
12
+ import { actionDialogResult } from '../../../api/actions'
13
+ import { useCNPGWriteGuard } from '../actions/useCNPGWriteGuard'
14
+ import { useToast } from '../../ui/Toast'
15
+ import { CNPG_FORM_FIELD } from '../formFields'
16
+
17
+ export function CNPGAttachArchiveDialog({
18
+ namespace,
19
+ name,
20
+ uid,
21
+ stores,
22
+ current,
23
+ onClose,
24
+ }: {
25
+ namespace: string
26
+ name: string
27
+ uid: string
28
+ stores: any[]
29
+ current?: { objectStore: string; serverName: string }
30
+ onClose: () => void
31
+ }) {
32
+ const { connection } = useConnection()
33
+ const [context] = useState(connection.context)
34
+ const [objectStore, setObjectStore] = useState(
35
+ current?.objectStore || (stores.length === 1 ? stores[0].metadata.name : ''),
36
+ )
37
+ const [serverName, setServerName] = useState(current?.serverName || `${name.slice(0, 54)}-${uid.slice(0, 8)}`)
38
+ const [acknowledged, setAcknowledged] = useState(false)
39
+ const preview = useCNPGArchivingPreview(namespace, name)
40
+ const write = useCNPGProtectionAction('clusters', namespace, name, preview.data?.context)
41
+ const guard = useCNPGWriteGuard({ namespace, name, scope: { kind: 'spec', paths: ['spec.plugins'] } })
42
+ const { showSuccess } = useToast()
43
+ const changed = connection.context !== context
44
+ const reviewed = preview.data
45
+ const upgrade = getRadarUpgradeRequirement(write.error || preview.error)
46
+ const result = actionDialogResult(write.outcomeError ?? write.error, { error: reviewed ? undefined : 'Attachment could not be reviewed', unknown: 'An earlier write to this Cluster may have taken effect' })
47
+ ?? (preview.error ? { state: 'error' as const, message: preview.error.message, title: reviewed ? undefined : 'Attachment could not be reviewed' } : undefined)
48
+ return (
49
+ <ActionConfirmDialog
50
+ open
51
+ onClose={onClose}
52
+ onBack={
53
+ reviewed
54
+ ? () => {
55
+ preview.reset()
56
+ write.reset()
57
+ setAcknowledged(false)
58
+ }
59
+ : undefined
60
+ }
61
+ backLabel="Back to storage"
62
+ onConfirm={() => {
63
+ if (!reviewed) {
64
+ preview.mutate({ reviewedContext: context, objectStore: objectStore.trim(), serverName: serverName.trim() })
65
+ return
66
+ }
67
+ write.mutate(
68
+ {
69
+ kind: 'clusters',
70
+ action: 'configureArchiving',
71
+ request: {
72
+ reviewedContext: reviewed.context,
73
+ uid: reviewed.uid,
74
+ facts: reviewed.facts,
75
+ params: {
76
+ objectStore: reviewed.facts.objectStore,
77
+ serverName: reviewed.facts.serverName,
78
+ acknowledgeArchive: acknowledged,
79
+ },
80
+ },
81
+ },
82
+ {
83
+ onSuccess: () => {
84
+ showSuccess('Archive configuration saved. Check WAL uploads and a successful base backup next.')
85
+ onClose()
86
+ },
87
+ },
88
+ )
89
+ }}
90
+ title={reviewed ? 'Review archive attachment' : 'Choose existing backup storage'}
91
+ subject={{ kind: 'Cluster', namespace, name }}
92
+ context={context}
93
+ effect="Enable the Barman plugin for WAL archiving and base backups. The ObjectStore itself is unchanged."
94
+ disabledReason={
95
+ changed ? 'The context changed. Close this guide and start again in the current context.' : undefined
96
+ }
97
+ incompleteReason={
98
+ !reviewed && (!objectStore.trim() || !serverName.trim())
99
+ ? 'Choose a store and archive server name'
100
+ : reviewed && !acknowledged
101
+ ? 'Confirm this archive identity is reserved for this Cluster'
102
+ : undefined
103
+ }
104
+ guard={reviewed ? guard.node : undefined}
105
+ guardSatisfied={!upgrade && (!reviewed || guard.satisfied)}
106
+ confirmLabel={reviewed ? (reviewed.unchanged ? 'Keep configuration' : 'Enable archiving') : 'Review attachment'}
107
+ isLoading={preview.isPending || write.isPending}
108
+ result={upgrade && result?.state === 'error' ? undefined : result}
109
+ warnings={reviewed?.warnings}
110
+ writes={
111
+ reviewed
112
+ ? [
113
+ {
114
+ summary: `patch Cluster ${namespace}/${name}`,
115
+ detail: `spec.plugins: enable ${String(reviewed.plugin.name)} with barmanObjectName=${reviewed.facts.objectStore}, serverName=${reviewed.facts.serverName}; preserve other plugins and parameters`,
116
+ },
117
+ ]
118
+ : undefined
119
+ }
120
+ >
121
+ {upgrade && <RadarUpgradeNote requirement={upgrade} />}
122
+ {reviewed ? (
123
+ <div className="space-y-3">
124
+ <FactGrid>
125
+ <FactRow label="ObjectStore">{reviewed.facts.objectStore}</FactRow>
126
+ <FactRow label="Archive identity">{reviewed.facts.serverName}</FactRow>
127
+ <FactRow label="Destination">
128
+ <span className="break-all font-mono">{reviewed.destination}</span>
129
+ </FactRow>
130
+ {reviewed.endpoint && (
131
+ <FactRow label="Endpoint">
132
+ <span className="break-all font-mono">{reviewed.endpoint}</span>
133
+ </FactRow>
134
+ )}
135
+ </FactGrid>
136
+ <p className="text-xs text-theme-text-secondary">
137
+ Kubernetes accepted the server dry-run. This does not verify uploads or archive isolation. The selected
138
+ ObjectStore is re-read on confirmation; it is not locked against a concurrent change.
139
+ </p>
140
+ <label className="flex items-start gap-2 text-sm">
141
+ <input type="checkbox" checked={acknowledged} onChange={(event) => setAcknowledged(event.target.checked)} />
142
+ <span>I confirm this archive identity is reserved for {name}.</span>
143
+ </label>
144
+ </div>
145
+ ) : (
146
+ <div className="space-y-3">
147
+ <label className="block">
148
+ <span className="text-xs font-medium text-theme-text-secondary">ObjectStore · namespace {namespace}</span>
149
+ <Input
150
+ aria-label="ObjectStore name"
151
+ list="cnpg-protection-stores"
152
+ value={objectStore}
153
+ disabled={!!current?.objectStore || preview.isPending}
154
+ onChange={(event) => setObjectStore(event.target.value)}
155
+ className={CNPG_FORM_FIELD}
156
+ />
157
+ <datalist id="cnpg-protection-stores">
158
+ {stores.map((store) => (
159
+ <option key={store.metadata.name} value={store.metadata.name} />
160
+ ))}
161
+ </datalist>
162
+ </label>
163
+ <label className="block">
164
+ <span className="text-xs font-medium text-theme-text-secondary">Archive server name</span>
165
+ <Input
166
+ aria-label="Archive server name"
167
+ value={serverName}
168
+ disabled={!!current?.objectStore || preview.isPending}
169
+ onChange={(event) => setServerName(event.target.value)}
170
+ className={CNPG_FORM_FIELD}
171
+ />
172
+ </label>
173
+ <p className="text-xs text-theme-text-secondary">
174
+ This name separates this Cluster’s archive inside the storage destination. A restored Cluster must use a
175
+ different archive identity from its recovery source. Store names alone do not establish isolation.
176
+ </p>
177
+ <p className="text-xs text-theme-text-tertiary">
178
+ Enter an existing store directly if suggestions are unavailable. Creating stores, configuring credentials
179
+ and installing the plugin are separate prerequisites.
180
+ </p>
181
+ </div>
182
+ )}
183
+ </ActionConfirmDialog>
184
+ )
185
+ }
@@ -0,0 +1,196 @@
1
+ import { useState } from 'react'
2
+ import yaml from 'yaml'
3
+ import {
4
+ ActionConfirmDialog,
5
+ CNPGSchedulePreviewFacts,
6
+ ConfirmDialog,
7
+ FactGrid,
8
+ Input,
9
+ RadarUpgradeNote,
10
+ getRadarUpgradeRequirement,
11
+ useDebouncedValue,
12
+ } from '@skyhook-io/k8s-ui'
13
+ import { validateRFC1123Label } from '@skyhook-io/k8s-ui/utils/validators'
14
+ import { useCNPGDraftSchedulePreview } from '../../../api/cnpg-protection'
15
+ import { useConnection } from '../../../context/ConnectionContext'
16
+ import { CreateResourceDialog } from '../../shared/CreateResourceDialog'
17
+ import { CNPGScheduleInput } from './CNPGScheduleInput'
18
+ import { useToast } from '../../ui/Toast'
19
+ import { CNPG_FORM_FIELD } from '../formFields'
20
+
21
+ export function CNPGCreateScheduleDialog({
22
+ namespace,
23
+ cluster,
24
+ onClose,
25
+ onCreated,
26
+ }: {
27
+ namespace: string
28
+ cluster: string
29
+ onClose: () => void
30
+ onCreated: () => void
31
+ }) {
32
+ const { connection } = useConnection()
33
+ const [context] = useState(connection.context)
34
+ const [name, setName] = useState(`${cluster.slice(0, 56)}-backup`)
35
+ const [schedule, setSchedule] = useState('0 0 2 * * *')
36
+ const [immediate, setImmediate] = useState(false)
37
+ const [draft, setDraft] = useState<string | null>(null)
38
+ const [editing, setEditing] = useState(false)
39
+ const [replace, setReplace] = useState(false)
40
+ const debounced = useDebouncedValue(schedule.trim(), 300)
41
+ const preview = useCNPGDraftSchedulePreview(namespace, cluster, debounced, !!debounced)
42
+ const { showSuccess } = useToast()
43
+ const current =
44
+ debounced === schedule.trim() && preview.data?.schedule === debounced && !preview.isFetching
45
+ ? preview.data
46
+ : undefined
47
+ const generated = () =>
48
+ yaml.stringify({
49
+ apiVersion: 'postgresql.cnpg.io/v1',
50
+ kind: 'ScheduledBackup',
51
+ metadata: { name: name.trim(), namespace },
52
+ spec: {
53
+ cluster: { name: cluster },
54
+ method: 'plugin',
55
+ pluginConfiguration: { name: 'barman-cloud.cloudnative-pg.io' },
56
+ schedule: schedule.trim(),
57
+ immediate,
58
+ },
59
+ })
60
+ const upgrade = getRadarUpgradeRequirement(preview.error)
61
+ const changed = context !== connection.context
62
+ if (changed)
63
+ return (
64
+ <ActionConfirmDialog
65
+ open
66
+ onClose={onClose}
67
+ onConfirm={() => {}}
68
+ title="Create backup schedule"
69
+ subject={{ kind: 'Cluster', namespace, name: cluster }}
70
+ context={context}
71
+ effect="The context changed."
72
+ confirmLabel="Review manifest"
73
+ disabledReason="Close this dialog and start again in the current context."
74
+ />
75
+ )
76
+ if (replace)
77
+ return (
78
+ <ConfirmDialog
79
+ open
80
+ onClose={() => setReplace(false)}
81
+ onConfirm={() => {
82
+ setDraft(generated())
83
+ setEditing(true)
84
+ setReplace(false)
85
+ }}
86
+ title="Replace the current schedule YAML?"
87
+ message="This uses the setup choices below and discards the YAML you edited."
88
+ variant="warning"
89
+ showWarning={false}
90
+ confirmLabel="Replace YAML"
91
+ />
92
+ )
93
+ if (editing && draft !== null)
94
+ return (
95
+ <CreateResourceDialog
96
+ open
97
+ onClose={onClose}
98
+ onBack={(value) => {
99
+ setDraft(value)
100
+ setEditing(false)
101
+ }}
102
+ backLabel="Back to schedule setup"
103
+ initialYaml={draft}
104
+ initialMode="create"
105
+ lockMode
106
+ title="Create backup schedule"
107
+ onCreated={(result) => {
108
+ onCreated()
109
+ showSuccess(
110
+ `${result.kind} ${result.name} created. Check its settings and the next backup; creation alone does not prove protection.`,
111
+ )
112
+ }}
113
+ />
114
+ )
115
+ return (
116
+ <ActionConfirmDialog
117
+ open
118
+ onClose={onClose}
119
+ onConfirm={() => {
120
+ if (draft !== null && draft !== generated()) {
121
+ setReplace(true)
122
+ return
123
+ }
124
+ setDraft(generated())
125
+ setEditing(true)
126
+ }}
127
+ title="Create matching backup schedule"
128
+ subject={{ kind: 'Cluster', namespace, name: cluster }}
129
+ context={context}
130
+ effect="Create a separate ScheduledBackup using this Cluster’s Barman plugin, then review its exact manifest."
131
+ size="wide"
132
+ confirmLabel="Review manifest"
133
+ guardSatisfied={!upgrade}
134
+ disabledReason={preview.error && !upgrade ? `Timing could not be checked: ${preview.error.message}` : undefined}
135
+ incompleteReason={
136
+ !name.trim()
137
+ ? 'Enter a schedule name'
138
+ : !validateRFC1123Label(name.trim()).valid
139
+ ? 'Use a valid schedule name'
140
+ : !current
141
+ ? 'Checking the schedule…'
142
+ : !current.valid
143
+ ? 'Choose a schedule the operator can run'
144
+ : undefined
145
+ }
146
+ notes={['Each scheduled run creates a Backup. This does not change the Cluster or validate credentials.']}
147
+ >
148
+ {upgrade && <RadarUpgradeNote requirement={upgrade} />}
149
+ {draft !== null && (
150
+ <div className="space-y-2 text-xs text-theme-text-secondary">
151
+ <p>
152
+ The current YAML is retained. Returning to review keeps it; rebuilding from these fields requires
153
+ confirmation.
154
+ </p>
155
+ <button type="button" className="btn-secondary px-3 py-1.5" onClick={() => setEditing(true)}>
156
+ Continue editing current YAML
157
+ </button>
158
+ </div>
159
+ )}
160
+ <label className="block">
161
+ <span className="text-xs font-medium text-theme-text-secondary">Schedule name · namespace {namespace}</span>
162
+ <Input
163
+ aria-label="Schedule name"
164
+ value={name}
165
+ onChange={(event) => setName(event.target.value)}
166
+ className={CNPG_FORM_FIELD}
167
+ />
168
+ </label>
169
+ <CNPGScheduleInput value={schedule} onChange={setSchedule} />
170
+ <label className="flex items-start gap-2 text-sm">
171
+ <input type="checkbox" checked={immediate} onChange={(event) => setImmediate(event.target.checked)} />
172
+ <span>
173
+ Also request a backup when the schedule is created.
174
+ <span className="mt-1 block text-xs text-theme-text-tertiary">
175
+ Off by default. Otherwise the first backup waits for its scheduled run.
176
+ </span>
177
+ </span>
178
+ </label>
179
+ {current && (
180
+ <FactGrid>
181
+ <CNPGSchedulePreviewFacts preview={current} />
182
+ </FactGrid>
183
+ )}
184
+ {preview.error && (
185
+ <button
186
+ type="button"
187
+ className="text-xs text-accent-text hover:underline"
188
+ onClick={() => void preview.refetch()}
189
+ disabled={preview.isFetching}
190
+ >
191
+ Retry timing check
192
+ </button>
193
+ )}
194
+ </ActionConfirmDialog>
195
+ )
196
+ }
@@ -0,0 +1,121 @@
1
+ // @vitest-environment jsdom
2
+ import { renderToStaticMarkup } from 'react-dom/server'
3
+ import { MemoryRouter } from 'react-router-dom'
4
+ import { beforeEach, expect, it, vi } from 'vitest'
5
+ import { CNPGProtectionSetup } from './CNPGProtectionSetup'
6
+
7
+ const state = vi.hoisted(() => ({
8
+ support: 'supported',
9
+ context: 'ctx',
10
+ cluster: {} as any,
11
+ schedules: [] as any[],
12
+ coverage: 'full',
13
+ wal: 'Not reported',
14
+ walState: 'unknown',
15
+ backup: 'No successful backup yet',
16
+ }))
17
+ vi.mock('../../../api/client', () => ({ useRadarFeature: () => ({ support: state.support }) }))
18
+ vi.mock('../../../context/ConnectionContext', () => ({
19
+ useConnection: () => ({ connection: { context: state.context } }),
20
+ }))
21
+ vi.mock('../useCNPGSidebarWorkspace', () => ({
22
+ useCNPGFleet: () => ({
23
+ query: {
24
+ data: {
25
+ objects: { objectStores: [], scheduledBackups: state.schedules },
26
+ coverage: { scheduledBackups: { state: state.coverage } },
27
+ },
28
+ refetch: vi.fn(),
29
+ },
30
+ fleet: {
31
+ rows: [
32
+ {
33
+ namespace: 'db',
34
+ name: 'pg',
35
+ cluster: state.cluster,
36
+ protection: { walArchiving: { text: state.wal, state: state.walState }, lastSuccessfulBackup: { text: state.backup } },
37
+ },
38
+ ],
39
+ },
40
+ }),
41
+ }))
42
+ vi.mock('../../../api/cnpg', () => ({
43
+ useCNPGOperator: () => ({
44
+ data: { components: [{ pluginName: 'barman-cloud.cloudnative-pg.io' }], coverage: { services: { state: 'full' } } },
45
+ }),
46
+ useCNPGClusterCapabilities: () => ({
47
+ data: { actions: { backup: { allowed: false, reason: 'You need create backups in namespace db' } } },
48
+ }),
49
+ }))
50
+ vi.mock('@skyhook-io/k8s-ui', async (importOriginal) => ({
51
+ ...(await importOriginal<typeof import('@skyhook-io/k8s-ui')>()),
52
+ DialogPortal: ({ children }: any) => children,
53
+ }))
54
+
55
+ beforeEach(() => {
56
+ state.support = 'supported'
57
+ state.cluster = { metadata: { name: 'pg', namespace: 'db', uid: 'uid' }, spec: {} }
58
+ state.schedules = []
59
+ state.coverage = 'full'
60
+ state.walState = 'unknown'
61
+ state.wal = 'Not reported'
62
+ state.backup = 'No successful backup yet'
63
+ })
64
+ const text = () =>
65
+ new DOMParser().parseFromString(renderToStaticMarkup(
66
+ <MemoryRouter initialEntries={['/?protectionSetup=1']}>
67
+ <CNPGProtectionSetup namespace="db" name="pg" onInspect={vi.fn()} />
68
+ </MemoryRouter>,
69
+ ), 'text/html').body.textContent!
70
+
71
+ it('presents the whole task without treating a declaration or an unread fact as protection', () => {
72
+ const html = text()
73
+ expect(html).toContain('1. Archive storage')
74
+ expect(html).toContain('2. Backup schedule')
75
+ expect(html).toContain('3. Verify protection')
76
+ expect(html).toContain('Attach the Barman archive destination first')
77
+ expect(html).toContain('You need create backups in namespace db')
78
+ expect(html).toContain('Not reported')
79
+ expect(html).not.toContain('Protection complete')
80
+ })
81
+
82
+ it('makes a suspended matching schedule reachable rather than presenting active protection', () => {
83
+ state.cluster.spec.plugins = [
84
+ { name: 'barman-cloud.cloudnative-pg.io', isWALArchiver: true, parameters: { barmanObjectName: 'store' } },
85
+ ]
86
+ state.schedules = [
87
+ {
88
+ metadata: { name: 'nightly', namespace: 'db' },
89
+ spec: {
90
+ cluster: { name: 'pg' },
91
+ method: 'plugin',
92
+ pluginConfiguration: { name: 'barman-cloud.cloudnative-pg.io' },
93
+ schedule: '@daily',
94
+ suspend: true,
95
+ },
96
+ },
97
+ ]
98
+ expect(text()).toContain('suspended: open the schedule to resume it')
99
+ expect(text()).not.toContain('Create matching schedule…')
100
+ })
101
+
102
+ it('does not interpret denied schedule inventory as an absent schedule', () => {
103
+ state.coverage = 'denied'
104
+ expect(text()).toContain('Inventory incomplete')
105
+ expect(text()).toContain('Schedules may exist outside the readable inventory')
106
+ })
107
+
108
+ it('shows the upgrade path before offering setup writes against an older Radar', () => {
109
+ state.support = 'unsupported'
110
+ expect(text()).toContain('need a newer Radar')
111
+ expect(text()).not.toContain('Choose existing ObjectStore')
112
+ })
113
+
114
+ it('puts failed uploads ahead of the setup milestones and links to the existing repair flow', () => {
115
+ state.wal = 'Failing'
116
+ state.walState = 'failing'
117
+ const html = new DOMParser().parseFromString(renderToStaticMarkup(<MemoryRouter initialEntries={['/?protectionSetup=1']}><CNPGProtectionSetup namespace="db" name="pg" onInspect={vi.fn()} onOpenArchivingRepair={vi.fn()} /></MemoryRouter>), 'text/html').body.textContent!
118
+ expect(html.indexOf('Repair WAL archiving first')).toBeLessThan(html.indexOf('1. Archive storage'))
119
+ expect(html).toContain('Open archiving repair')
120
+ expect(html).toContain('Uploads failing')
121
+ })