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