@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
@@ -1,18 +1,23 @@
1
1
  import { useCallback } from 'react'
2
- import { useSearchParams } from 'react-router-dom'
3
- import { WorkloadLogsViewer, type WorkloadLogsFetchParams, type WorkloadLogsResult } from '@skyhook-io/k8s-ui'
4
- import { fetchJSON } from '../../api/client'
2
+ import { useLocation, useSearchParams } from 'react-router-dom'
3
+ import { PaneLoader, WorkloadLogsViewer, type WorkloadLogsFetchParams, type WorkloadLogsResult } from '@skyhook-io/k8s-ui'
4
+ import { fetchJSON, useCapabilities, useRadarFeature } from '../../api/client'
5
5
  import { getApiBase, getCredentialsMode } from '../../api/config'
6
6
  import { useDesktopDownload } from '../../hooks/useDesktopDownload'
7
7
  import { useTheme } from '../../context/ThemeContext'
8
+ import { CNPGIntervalBanner, useCNPGIntervalParams } from './CNPGTrends'
9
+ import { useCNPGFleet } from './useCNPGSidebarWorkspace'
10
+ import { useCNPGNavigate } from './useCNPGNavigate'
11
+ import { cnpgClusterProblemsPath, cnpgWithinDetail } from './paths'
8
12
 
9
13
  function logsPath(namespace: string, name: string) {
10
14
  return `/cnpg/clusters/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/logs`
11
15
  }
12
16
 
13
- function query(params: WorkloadLogsFetchParams, tailDefault?: number) {
17
+ export function query(params: WorkloadLogsFetchParams, tailDefault?: number, allContainersSupported = true) {
14
18
  const q = new URLSearchParams()
15
- if (params.container) q.set('container', params.container)
19
+ const container = params.container || (allContainersSupported ? 'all' : '')
20
+ if (container && (container !== 'all' || allContainersSupported)) q.set('container', container)
16
21
  const tail = params.tailLines ?? tailDefault
17
22
  if (tail) q.set('tailLines', String(tail))
18
23
  if (params.sinceSeconds) q.set('sinceSeconds', String(params.sinceSeconds))
@@ -20,43 +25,95 @@ function query(params: WorkloadLogsFetchParams, tailDefault?: number) {
20
25
  return s ? `?${s}` : ''
21
26
  }
22
27
 
28
+ // An interval is read from its start with both bounds applied server-side; the
29
+ // viewer's line-count and since selectors do not apply to it.
30
+ export function intervalQuery(params: WorkloadLogsFetchParams, since: string, until: string) {
31
+ const q = new URLSearchParams({ sinceTime: since, untilTime: until })
32
+ q.set('container', params.container || 'all')
33
+ return `?${q.toString()}`
34
+ }
35
+
23
36
  /**
24
37
  * Logs merged from every instance Pod of a CloudNativePG Cluster. `?pod=`
25
- * preselects one instance (the fleet's "Logs" action and "Open instance logs").
38
+ * preselects one instance (the fleet's "Logs" action and "Open instance logs"),
39
+ * `?container=` one container (e.g. the plugin-barman-cloud sidecar);
40
+ * `?since=&until=` bounds them to an interval selected on a History chart.
26
41
  */
27
42
  export function CNPGClusterLogs({ namespace, name }: { namespace: string; name: string }) {
28
43
  const [searchParams] = useSearchParams()
44
+ const navigate = useCNPGNavigate()
45
+ const location = useLocation()
46
+ const { fleet } = useCNPGFleet([namespace])
47
+ const row = fleet?.rows.find((r) => r.namespace === namespace && r.name === name)
48
+ const problem = row?.problems.find((p) => p.job === 'initdb')
49
+ const overviewPath = `${cnpgClusterProblemsPath(namespace, name, searchParams.get('ctx') ?? undefined)}&tab=overview`
50
+ const inPlace = cnpgWithinDetail(location.pathname, location.search, overviewPath)
29
51
  const pod = searchParams.get('pod')
52
+ const container = searchParams.get('container')
53
+ const interval = useCNPGIntervalParams()
30
54
  const desktopDownload = useDesktopDownload()
31
55
  const { theme } = useTheme()
56
+ const since = interval?.since
57
+ const until = interval?.until
32
58
 
59
+ const { guard, support } = useRadarFeature('cnpgWorkspace')
60
+ const capabilities = useCapabilities()
61
+ const allContainersSupported = support === 'supported'
33
62
  const fetchAll = useCallback(
34
63
  (params: WorkloadLogsFetchParams) =>
35
- fetchJSON<WorkloadLogsResult>(`${logsPath(namespace, name)}${query(params)}`, { signal: params.signal }),
36
- [namespace, name],
64
+ since && until
65
+ ? guard(() => fetchJSON<WorkloadLogsResult>(`${logsPath(namespace, name)}${intervalQuery(params, since, until)}`, { signal: params.signal }))
66
+ : fetchJSON<WorkloadLogsResult>(`${logsPath(namespace, name)}${query(params, undefined, allContainersSupported)}`, { signal: params.signal }),
67
+ [guard, namespace, name, since, until, allContainersSupported],
37
68
  )
38
- const createStream = useCallback(
69
+ const stream = useCallback(
39
70
  (params: WorkloadLogsFetchParams) =>
40
- new EventSource(`${getApiBase()}${logsPath(namespace, name)}/stream${query(params, 50)}`, {
71
+ new EventSource(`${getApiBase()}${logsPath(namespace, name)}/stream${query(params, 50, allContainersSupported)}`, {
41
72
  withCredentials: getCredentialsMode() === 'include',
42
73
  }),
43
- [namespace, name],
74
+ [namespace, name, allContainersSupported],
44
75
  )
45
76
 
77
+ if (interval && support === 'unknown') return capabilities.isPending && capabilities.fetchStatus !== 'paused'
78
+ ? <PaneLoader label="Checking interval support…" className="flex-1" />
79
+ : <div className="space-y-2 p-3">
80
+ <CNPGIntervalBanner since={interval.since} until={interval.until} note="Interval log support could not be checked. Clear this interval to read ordinary logs." onClear={interval.clear} />
81
+ <button type="button" className="btn-secondary" disabled={capabilities.isFetching || capabilities.fetchStatus === 'paused'} onClick={() => void capabilities.refetch()}>{capabilities.fetchStatus === 'paused' ? 'Waiting for connection' : capabilities.isFetching ? 'Checking…' : 'Retry'}</button>
82
+ </div>
83
+
46
84
  return (
47
- <div className="h-full">
48
- <WorkloadLogsViewer
49
- key={pod ?? ''}
50
- name={name}
51
- fetchAll={fetchAll}
52
- createStream={createStream}
53
- overrideDownload={desktopDownload}
54
- forceDark={theme === 'dark' ? true : undefined}
55
- // Light logs on a dark app are never wanted: dark theme pins the palette,
56
- // light theme only sets where it starts and leaves the toggle available.
57
- defaultDark={false}
58
- initialPods={pod ? [pod] : undefined}
59
- />
85
+ <div className="flex h-full flex-col">
86
+ {interval && (
87
+ <div className="px-3 pt-3">
88
+ <CNPGIntervalBanner
89
+ since={interval.since}
90
+ until={interval.until}
91
+ note={support === 'unsupported' ? 'This interval needs a newer Radar. Clear it to read ordinary logs.' : 'Selected on a History chart. Lines from the start of the interval, up to 64 KiB per instance; the line-count selector does not apply and streaming is off.'}
92
+ onClear={interval.clear}
93
+ />
94
+ </div>
95
+ )}
96
+ <div className="min-h-0 flex-1">
97
+ <WorkloadLogsViewer
98
+ key={`${pod ?? ''}|${container ?? ''}|${since ?? ''}|${until ?? ''}|${allContainersSupported}`}
99
+ name={name}
100
+ allowAllContainers={allContainersSupported}
101
+ disableSourceControlsWithoutSource
102
+ emptySourceState={<div className="space-y-2 text-sm text-theme-text-secondary">
103
+ <p>No instance log source yet. {row?.hibernated ? 'The Cluster is hibernated.' : problem?.title}</p>
104
+ <button type="button" className="text-accent-text hover:underline" onClick={() => navigate(inPlace ?? overviewPath, { replace: !!inPlace, state: location.state })}>{problem ? 'See Overview’s problem →' : 'Open Overview →'}</button>
105
+ </div>}
106
+ fetchAll={fetchAll}
107
+ createStream={interval ? undefined : stream}
108
+ overrideDownload={desktopDownload}
109
+ forceDark={theme === 'dark' ? true : undefined}
110
+ // Light logs on a dark app are never wanted: dark theme pins the palette,
111
+ // light theme only sets where it starts and leaves the toggle available.
112
+ defaultDark={false}
113
+ initialPods={pod ? [pod] : undefined}
114
+ initialContainer={container && container !== 'all' ? container : allContainersSupported ? undefined : 'postgres'}
115
+ />
116
+ </div>
60
117
  </div>
61
118
  )
62
119
  }
@@ -0,0 +1,152 @@
1
+ import { renderToStaticMarkup } from 'react-dom/server'
2
+ import { MemoryRouter } from 'react-router-dom'
3
+ import { beforeEach, expect, it, vi } from 'vitest'
4
+ import type { CNPGDimension } from '@skyhook-io/k8s-ui'
5
+ import { CNPGBackupsTab, CNPGStorageTab } from './CNPGClusterTabs'
6
+ const state = vi.hoisted(() => ({
7
+ coverage: 'full',
8
+ primary: {} as any,
9
+ storageProps: {} as any,
10
+ schedules: [] as any[],
11
+ dimensions: [] as CNPGDimension[],
12
+ }))
13
+ const cluster = {
14
+ apiVersion: 'postgresql.cnpg.io/v1',
15
+ kind: 'Cluster',
16
+ metadata: { name: 'pg', namespace: 'db' },
17
+ status: { currentPrimary: 'pg-1' } as any,
18
+ spec: {} as any,
19
+ }
20
+ vi.mock('../../api/client', () => ({ useRadarFeature: () => ({ support: 'supported' }) }))
21
+ vi.mock('../../api/cnpg', () => ({
22
+ useCNPGRuntime: () => ({ data: { permission: { proxy: 'denied' }, instances: [state.primary] } }),
23
+ }))
24
+ vi.mock('./useCNPGClusterAssessment', () => ({
25
+ useCNPGClusterAssessment: () => ({
26
+ dimensions: state.dimensions,
27
+ row: {
28
+ cluster,
29
+ problems: [],
30
+ protection: { walArchiving: { text: 'Not archived: no destination configured', tone: 'neutral' } },
31
+ },
32
+ query: {
33
+ data: { coverage: { backups: { state: state.coverage } }, objects: { backups: [], clusters: [cluster] } },
34
+ },
35
+ runtime: {},
36
+ }),
37
+ }))
38
+ vi.mock('./useCNPGSidebarWorkspace', () => ({
39
+ useCNPGFleet: () => ({
40
+ query: {
41
+ data: {
42
+ installed: true,
43
+ coverage: { backups: { state: state.coverage } },
44
+ objects: { backups: [], scheduledBackups: state.schedules },
45
+ },
46
+ },
47
+ fleet: { rows: [{ name: 'pg', namespace: 'db', cluster }] },
48
+ }),
49
+ }))
50
+ vi.mock('../../api/cnpg-recovery', () => ({ useCNPGRestoreCapability: () => ({ data: { allowed: true } }) }))
51
+ vi.mock('./CNPGProtection', () => ({ CNPGProtection: () => null }))
52
+ vi.mock('./CNPGArchivingRepair', () => ({ CNPGArchivingRepair: () => null }))
53
+ vi.mock('./CNPGStorage', () => ({
54
+ CNPGStorage: (props: any) => {
55
+ state.storageProps = props
56
+ return null
57
+ },
58
+ }))
59
+ vi.mock('./recovery/CNPGRestoreButton', () => ({
60
+ CNPGRestoreButton: ({ disabledReason }: { disabledReason?: string }) => (
61
+ <button disabled={!!disabledReason}>Restore</button>
62
+ ),
63
+ }))
64
+ vi.mock('../../context/ConnectionContext', () => ({ useConnection: () => ({ connection: { context: 'test' } }) }))
65
+ beforeEach(() => {
66
+ state.coverage = 'full'
67
+ state.primary = {}
68
+ state.schedules = []
69
+ state.dimensions = []
70
+ cluster.spec = {}
71
+ delete cluster.status.lastSuccessfulBackup
72
+ })
73
+ it('passes primary identity through to Storage when database reads are denied', () => {
74
+ state.primary = { pod: 'pg-1', role: 'primary', metrics: { state: 'denied' } }
75
+ renderToStaticMarkup(<CNPGStorageTab namespace="db" name="pg" />)
76
+ expect(state.storageProps.primary).toBe(state.primary)
77
+ expect(state.storageProps.runtime.data.permission.proxy).toBe('denied')
78
+ })
79
+ it('uses the shared restore assessment for the Backups button', () => {
80
+ const render = () =>
81
+ renderToStaticMarkup(
82
+ <MemoryRouter>
83
+ <CNPGBackupsTab namespace="db" name="pg" onInspect={() => {}} />
84
+ </MemoryRouter>,
85
+ )
86
+ state.coverage = 'full'
87
+ expect(render()).toContain('disabled=""')
88
+ expect(render()).toContain('Nothing to restore from yet')
89
+ state.coverage = 'denied'
90
+ expect(render()).not.toContain('disabled=""')
91
+ expect(render()).not.toContain('Nothing to restore from yet')
92
+ })
93
+
94
+ it('starts a single guided setup path beside restore, without treating a schedule as protection', () => {
95
+ state.schedules = [{ metadata: { name: 'pg-nightly', namespace: 'db' }, spec: { cluster: { name: 'pg' } } }]
96
+ const html = renderToStaticMarkup(
97
+ <MemoryRouter>
98
+ <CNPGBackupsTab namespace="db" name="pg" onInspect={() => {}} onOpenYaml={() => {}} />
99
+ </MemoryRouter>,
100
+ )
101
+ expect(html).toContain('Set up backups…')
102
+ expect(html).toContain('Nothing to restore from yet')
103
+ expect(html).not.toContain('Configure spec.backup.barmanObjectStore')
104
+ })
105
+
106
+ it('keeps the protection verdict ahead of setup without repeating the blocker', () => {
107
+ state.schedules = [{ metadata: { name: 'pg-nightly', namespace: 'db' }, spec: { cluster: { name: 'pg' } } }]
108
+ state.dimensions = [
109
+ {
110
+ id: 'protection',
111
+ label: 'Backups',
112
+ tone: 'degraded',
113
+ text: 'Backup schedule pg-nightly cannot run: no backup destination',
114
+ source: 'ScheduledBackup method against its target Cluster spec',
115
+ },
116
+ ]
117
+ const html = renderToStaticMarkup(
118
+ <MemoryRouter>
119
+ <CNPGBackupsTab namespace="db" name="pg" onInspect={() => {}} onOpenYaml={() => {}} />
120
+ </MemoryRouter>,
121
+ )
122
+ expect(html.match(/cannot run:/g)).toHaveLength(1)
123
+ expect(html.indexOf(state.dimensions[0].text)).toBeLessThan(html.indexOf('Set up backups…'))
124
+ })
125
+
126
+ it('does not invent a missing destination for an enabled third-party plugin', () => {
127
+ cluster.spec.plugins = [{ name: 'third-party-backup' }]
128
+ const html = renderToStaticMarkup(
129
+ <MemoryRouter>
130
+ <CNPGBackupsTab namespace="db" name="pg" onInspect={() => {}} />
131
+ </MemoryRouter>,
132
+ )
133
+ expect(html).not.toContain('Configure spec.backup')
134
+ delete cluster.spec.plugins
135
+ })
136
+
137
+ it('passes recovery certainty instead of equating a destination with a usable backup', () => {
138
+ const renderStorage = () => renderToStaticMarkup(<CNPGStorageTab namespace="db" name="pg" />)
139
+ state.coverage = 'full'
140
+ cluster.spec.backup = { barmanObjectStore: { destinationPath: 's3://backups' } }
141
+ renderStorage()
142
+ expect(state.storageProps.restoreState).toBe('none')
143
+ cluster.status.lastSuccessfulBackup = '2026-10-01T00:00:00Z'
144
+ renderStorage()
145
+ expect(state.storageProps.restoreState).toBe('available')
146
+ delete cluster.status.lastSuccessfulBackup
147
+ state.coverage = 'denied'
148
+ renderStorage()
149
+ expect(state.storageProps.restoreState).toBe('unknown')
150
+ state.coverage = 'full'
151
+ delete cluster.spec.backup
152
+ })
@@ -0,0 +1,187 @@
1
+ import { useRef, type ReactNode } from 'react'
2
+ import type { RenderDiagnoseAction } from '../../context/DiagnoseCustomization'
3
+ import { useSearchParams } from 'react-router-dom'
4
+ import { CNPGDimensionMark, CNPGDimensionVerdict, CNPGServingStatus, coverageReadable, toneTextClass, type CNPGDimension, type CNPGFleetRow } from '@skyhook-io/k8s-ui'
5
+ import type { SelectedResource } from '../../types'
6
+ import { useCNPGRuntime } from '../../api/cnpg'
7
+ import { CNPGStorage } from './CNPGStorage'
8
+ import { CNPGArchivingRepair } from './CNPGArchivingRepair'
9
+ import { CNPGProtection } from './CNPGProtection'
10
+ import { CNPGRestoreValidation } from './recovery/CNPGRestoreValidation'
11
+ import { CNPGRestoreButton } from './recovery/CNPGRestoreButton'
12
+ import { assessRestoreSources } from './recovery/restoreModel'
13
+ import { useCNPGRestoreCapability } from '../../api/cnpg-recovery'
14
+ import { CNPGScreenGate } from './shared'
15
+ import { useCNPGClusterAssessment } from './useCNPGClusterAssessment'
16
+ import { useCNPGFleet } from './useCNPGSidebarWorkspace'
17
+ import { CNPGProtectionSetup } from './protection/CNPGProtectionSetup'
18
+
19
+ /** The health mark beside a tab: the same assessment the Overview explains, for the dimension that tab holds. */
20
+ export function CNPGTabMark({ namespace, name, id }: { namespace: string; name: string; id: CNPGDimension['id'] }) {
21
+ const { dimensions } = useCNPGClusterAssessment(namespace, name)
22
+ const dimension = dimensions?.find((d) => d.id === id)
23
+ return dimension ? <CNPGDimensionMark dimension={dimension} /> : null
24
+ }
25
+
26
+ /** Why a tab carries its mark, as the tab's first line; nothing when the dimension is fine. */
27
+ export function CNPGTabVerdict({ namespace, name, id, className, alwaysShow }: { namespace: string; name: string; id: CNPGDimension['id']; className?: string; alwaysShow?: boolean }) {
28
+ const { dimensions } = useCNPGClusterAssessment(namespace, name)
29
+ const dimension = dimensions?.find((d) => d.id === id)
30
+ return dimension ? <CNPGDimensionVerdict dimension={dimension} className={className} alwaysShow={alwaysShow} /> : null
31
+ }
32
+
33
+ /** Whether the cluster serves writes, on its title line, opening the tab that explains it. */
34
+ export function CNPGClusterServing({ namespace, name, onSelect }: { namespace: string; name: string; onSelect: () => void }) {
35
+ const { dimensions } = useCNPGClusterAssessment(namespace, name)
36
+ const dimension = dimensions?.find((d) => d.id === 'serving')
37
+ return dimension ? <CNPGServingStatus dimension={dimension} onSelect={onSelect} /> : null
38
+ }
39
+
40
+ /** Storage: volumes, what holds WAL, and resize, with the history of both one click away. */
41
+ export function CNPGStorageTab({
42
+ namespace,
43
+ name,
44
+ onOpenHistory,
45
+ onOpenReplication,
46
+ }: {
47
+ namespace: string
48
+ name: string
49
+ onOpenHistory?: () => void
50
+ onOpenReplication?: () => void
51
+ }) {
52
+ const runtime = useCNPGRuntime(namespace, name)
53
+ const { row, query } = useCNPGClusterAssessment(namespace, name)
54
+ const clusterObject = row?.cluster
55
+ const primary = runtime.data?.instances.find((i) => i.role === 'primary')
56
+ return (
57
+ <div className="space-y-4 p-4">
58
+ <div className="flex flex-wrap items-center gap-3">
59
+ <h2 className="text-sm font-semibold text-theme-text-primary">Volumes and WAL</h2>
60
+ {onOpenHistory && (
61
+ <button type="button" onClick={onOpenHistory} className="ml-auto text-xs text-accent-text hover:underline">
62
+ Storage history →
63
+ </button>
64
+ )}
65
+ </div>
66
+ <CNPGTabVerdict namespace={namespace} name={name} id="storage" />
67
+ {row && <SlotRelief row={row} onOpenReplication={onOpenReplication} />}
68
+ <CNPGStorage namespace={namespace} name={name} primary={primary} runtime={runtime} clusterObject={clusterObject} restoreState={assessRestoreSources(query.data, namespace, clusterObject).recoveryState} walArchiving={row?.protection.walArchiving} />
69
+ </div>
70
+ )
71
+ }
72
+
73
+ /**
74
+ * What to do about WAL an inactive slot holds: the standby it is kept for,
75
+ * whether that standby receives anything, and the two ways the WAL is freed.
76
+ */
77
+ function SlotRelief({ row, onOpenReplication }: { row: CNPGFleetRow; onOpenReplication?: () => void }) {
78
+ const slots = row.problems.filter((p) => p.reason === 'CNPGInactiveSlot')
79
+ if (slots.length === 0) return null
80
+ const gaps = new Set(row.problems.filter((p) => p.reason === 'CNPGStandbyNotReceiving').map((p) => p.subject.name))
81
+ return (
82
+ <section className="rounded-xl border border-theme-border bg-theme-surface px-4 py-3 shadow-theme-sm">
83
+ {slots.map((p) => {
84
+ const standby = p.instance
85
+ return (
86
+ <div key={p.id} className="space-y-1 text-sm">
87
+ <div className={toneTextClass('degraded')}>{p.title}</div>
88
+ <p className="text-theme-text-secondary">
89
+ The primary keeps that WAL until {standby ?? 'the slot’s consumer'} catches up or the slot is dropped.
90
+ {standby && gaps.has(standby) ? ` ${standby} is not streaming from the primary, and the slot advances only while it does — even if ${standby} replays WAL from the archive meanwhile.` : ''}
91
+ </p>
92
+ {standby && (
93
+ <p className="text-theme-text-secondary">
94
+ To free it, get {standby} streaming again — restart it, after checking its logs for why it stopped — or destroy it so CloudNativePG recreates it from
95
+ a fresh copy of the primary; the operator drops the HA slot of an instance that no longer exists.
96
+ </p>
97
+ )}
98
+ {onOpenReplication && (
99
+ <button type="button" onClick={onOpenReplication} className="text-xs text-accent-text hover:underline">
100
+ {standby ? `${standby} on Replication →` : 'Replication →'}
101
+ </button>
102
+ )}
103
+ </div>
104
+ )
105
+ })}
106
+ </section>
107
+ )
108
+ }
109
+
110
+ /** Backups: this cluster's recovery evidence, runs, schedules and destination, plus restore validation once it was restored. */
111
+ export function CNPGBackupsTab({
112
+ namespace,
113
+ name,
114
+ onInspect,
115
+ onOpenLogs,
116
+ onOpenOperator,
117
+ onOpenYaml,
118
+ }: {
119
+ namespace: string
120
+ name: string
121
+ onInspect: (r: SelectedResource) => void
122
+ onOpenLogs?: (pod: string, container: string) => void
123
+ onOpenOperator?: () => void
124
+ onOpenYaml?: () => void
125
+ }) {
126
+ const { query, fleet } = useCNPGFleet([namespace])
127
+ const { row, runtime } = useCNPGClusterAssessment(namespace, name)
128
+ const primary = runtime.data?.instances.find((i) => i.role === 'primary')
129
+ const [searchParams] = useSearchParams()
130
+ const archivingRepairRef = useRef<HTMLDivElement>(null)
131
+ const restore = useCNPGRestoreCapability(namespace)
132
+ const restoreBlocked = restore.data ? (restore.data.allowed ? undefined : restore.data.reason ?? 'Not allowed') : restore.isLoading ? 'Checking whether you can create a Cluster here…' : undefined
133
+ return (
134
+ <CNPGScreenGate query={query} fleet={fleet}>
135
+ {(data, readyFleet) => {
136
+ const cluster = readyFleet.rows.find((r) => r.namespace === namespace && r.name === name)?.cluster
137
+ const nothing = assessRestoreSources(data, namespace, cluster).disabledReason
138
+ return (
139
+ <div className="flex min-h-0 flex-1 flex-col">
140
+ <CNPGTabVerdict namespace={namespace} name={name} id="protection" className="px-4 pt-4" />
141
+ <div className="flex flex-wrap items-center gap-2 px-4 pt-3">
142
+ <CNPGProtectionSetup key={`${namespace}/${name}`} namespace={namespace} name={name} onInspect={onInspect} onOpenYaml={onOpenYaml} onOpenOperator={onOpenOperator} onOpenArchivingRepair={() => archivingRepairRef.current?.scrollIntoView({ block: 'start' })} />
143
+ <CNPGRestoreButton namespace={namespace} entry={{ kind: 'cluster', name }} disabledReason={restoreBlocked ?? nothing} />
144
+ <span className="text-xs text-theme-text-tertiary">{restoreBlocked ?? nothing ?? 'Restores into a new Cluster beside this one; this cluster is not changed.'}</span>
145
+ </div>
146
+ {row && (
147
+ <div ref={archivingRepairRef} className="px-4 pt-4">
148
+ <CNPGArchivingRepair
149
+ row={row}
150
+ primary={primary}
151
+ objectStores={data.objects.objectStores ?? []}
152
+ backups={coverageReadable(data.coverage?.backups ?? { state: 'notInstalled' }, namespace) ? (data.objects.backups ?? []) : null}
153
+ onOpenLogs={onOpenLogs}
154
+ onOpenOperator={onOpenOperator}
155
+ onInspect={onInspect}
156
+ />
157
+ </div>
158
+ )}
159
+ {readyFleet.rows.find((r) => r.namespace === namespace && r.name === name)?.cluster?.spec?.bootstrap?.recovery && (
160
+ <CNPGRestoreValidation namespace={namespace} name={name} />
161
+ )}
162
+ <CNPGProtection
163
+ data={data}
164
+ fleet={readyFleet}
165
+ namespaces={[namespace]}
166
+ searchParams={searchParams}
167
+ onSetParams={() => {}}
168
+ onInspect={onInspect}
169
+ inspected={null}
170
+ onClearNamespaces={() => {}}
171
+ scopeCluster={{ namespace, name }}
172
+ />
173
+ </div>
174
+ )
175
+ }}
176
+ </CNPGScreenGate>
177
+ )
178
+ }
179
+
180
+ export function CNPGInvestigationAction({ namespace, name, render, context }: {
181
+ namespace: string; name: string; render: RenderDiagnoseAction; context: Parameters<RenderDiagnoseAction>[0]
182
+ }): ReactNode {
183
+ const { row, dimensions } = useCNPGClusterAssessment(namespace, name)
184
+ const problem = !!row?.problems.length || dimensions?.some((d) => ['degraded', 'alert', 'unhealthy'].includes(d.tone))
185
+ const assessed = !!row && !!dimensions && dimensions.every((d) => d.tone !== 'unknown')
186
+ return render({ ...context, health: problem ? 'problem' : assessed ? 'healthy' : 'unknown' })
187
+ }
@@ -0,0 +1,46 @@
1
+ import type { SelectedResource } from '../../types'
2
+ import { CNPGOverview } from './CNPGOverview'
3
+ import { CNPGScreenGate } from './shared'
4
+ import { useCNPGFleet } from './useCNPGSidebarWorkspace'
5
+ import { useCNPGScreenParams } from './useCNPGScreenParams'
6
+
7
+ /**
8
+ * The CloudNativePG Cluster kind's list on its Resources route: the Clusters
9
+ * view of /cnpg, over the same data and URL parameters, inspecting rows in the
10
+ * Resources page's drawer.
11
+ */
12
+ export function CNPGClustersKindView({
13
+ namespaces,
14
+ inspected,
15
+ onInspect,
16
+ onClearNamespaces,
17
+ onCreate,
18
+ }: {
19
+ namespaces: string[]
20
+ inspected: SelectedResource | null
21
+ onInspect: (resource: SelectedResource) => void
22
+ onClearNamespaces: () => void
23
+ onCreate: () => void
24
+ }) {
25
+ const { query, fleet } = useCNPGFleet(namespaces)
26
+ const [searchParams, setParams] = useCNPGScreenParams()
27
+ return (
28
+ <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-theme-base">
29
+ <CNPGScreenGate query={query} fleet={fleet}>
30
+ {(data, readyFleet) => (
31
+ <CNPGOverview
32
+ data={data}
33
+ fleet={readyFleet}
34
+ namespaces={namespaces}
35
+ searchParams={searchParams}
36
+ onSetParams={setParams}
37
+ onInspect={onInspect}
38
+ inspected={inspected}
39
+ onClearNamespaces={onClearNamespaces}
40
+ onCreate={onCreate}
41
+ />
42
+ )}
43
+ </CNPGScreenGate>
44
+ </div>
45
+ )
46
+ }