@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.
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/App.tsx +125 -87
- 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.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 +266 -39
- 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/radarFeatures.render.test.tsx +47 -3
- package/src/api/radarFeatures.ts +4 -1
- 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/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 +6 -55
- package/src/components/capacity/CapacityView.test.tsx +323 -0
- 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 +11 -129
- 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 +228 -122
- 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/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/gitops/ArgoResourceDiffLoader.tsx +4 -2
- package/src/components/gitops/GitOpsView.tsx +16 -23
- 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/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/HomeView.tsx +20 -9
- package/src/components/home/ProblemsPanel.test.tsx +81 -0
- package/src/components/home/TopologyPreview.tsx +2 -2
- 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/settings/LocalIntegrationStatus.test.tsx +52 -0
- package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
- package/src/components/settings/SettingsDialog.tsx +28 -15
- package/src/components/timeline/TimelineList.tsx +22 -24
- package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
- package/src/components/timeline/TimelineLoadError.tsx +96 -0
- package/src/components/timeline/TimelineView.tsx +18 -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/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 +10 -1
- package/src/components/workload/WorkloadView.tsx +27 -239
- 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/contexts/CapabilitiesContext.tsx +1 -1
- 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
|
+
}
|