@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,185 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ActionConfirmDialog,
|
|
4
|
+
FactGrid,
|
|
5
|
+
FactRow,
|
|
6
|
+
Input,
|
|
7
|
+
RadarUpgradeNote,
|
|
8
|
+
getRadarUpgradeRequirement,
|
|
9
|
+
} from '@skyhook-io/k8s-ui'
|
|
10
|
+
import { useCNPGArchivingPreview, useCNPGProtectionAction } from '../../../api/cnpg-protection'
|
|
11
|
+
import { useConnection } from '../../../context/ConnectionContext'
|
|
12
|
+
import { actionDialogResult } from '../../../api/actions'
|
|
13
|
+
import { useCNPGWriteGuard } from '../actions/useCNPGWriteGuard'
|
|
14
|
+
import { useToast } from '../../ui/Toast'
|
|
15
|
+
import { CNPG_FORM_FIELD } from '../formFields'
|
|
16
|
+
|
|
17
|
+
export function CNPGAttachArchiveDialog({
|
|
18
|
+
namespace,
|
|
19
|
+
name,
|
|
20
|
+
uid,
|
|
21
|
+
stores,
|
|
22
|
+
current,
|
|
23
|
+
onClose,
|
|
24
|
+
}: {
|
|
25
|
+
namespace: string
|
|
26
|
+
name: string
|
|
27
|
+
uid: string
|
|
28
|
+
stores: any[]
|
|
29
|
+
current?: { objectStore: string; serverName: string }
|
|
30
|
+
onClose: () => void
|
|
31
|
+
}) {
|
|
32
|
+
const { connection } = useConnection()
|
|
33
|
+
const [context] = useState(connection.context)
|
|
34
|
+
const [objectStore, setObjectStore] = useState(
|
|
35
|
+
current?.objectStore || (stores.length === 1 ? stores[0].metadata.name : ''),
|
|
36
|
+
)
|
|
37
|
+
const [serverName, setServerName] = useState(current?.serverName || `${name.slice(0, 54)}-${uid.slice(0, 8)}`)
|
|
38
|
+
const [acknowledged, setAcknowledged] = useState(false)
|
|
39
|
+
const preview = useCNPGArchivingPreview(namespace, name)
|
|
40
|
+
const write = useCNPGProtectionAction('clusters', namespace, name, preview.data?.context)
|
|
41
|
+
const guard = useCNPGWriteGuard({ namespace, name, scope: { kind: 'spec', paths: ['spec.plugins'] } })
|
|
42
|
+
const { showSuccess } = useToast()
|
|
43
|
+
const changed = connection.context !== context
|
|
44
|
+
const reviewed = preview.data
|
|
45
|
+
const upgrade = getRadarUpgradeRequirement(write.error || preview.error)
|
|
46
|
+
const result = actionDialogResult(write.outcomeError ?? write.error, { error: reviewed ? undefined : 'Attachment could not be reviewed', unknown: 'An earlier write to this Cluster may have taken effect' })
|
|
47
|
+
?? (preview.error ? { state: 'error' as const, message: preview.error.message, title: reviewed ? undefined : 'Attachment could not be reviewed' } : undefined)
|
|
48
|
+
return (
|
|
49
|
+
<ActionConfirmDialog
|
|
50
|
+
open
|
|
51
|
+
onClose={onClose}
|
|
52
|
+
onBack={
|
|
53
|
+
reviewed
|
|
54
|
+
? () => {
|
|
55
|
+
preview.reset()
|
|
56
|
+
write.reset()
|
|
57
|
+
setAcknowledged(false)
|
|
58
|
+
}
|
|
59
|
+
: undefined
|
|
60
|
+
}
|
|
61
|
+
backLabel="Back to storage"
|
|
62
|
+
onConfirm={() => {
|
|
63
|
+
if (!reviewed) {
|
|
64
|
+
preview.mutate({ reviewedContext: context, objectStore: objectStore.trim(), serverName: serverName.trim() })
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
write.mutate(
|
|
68
|
+
{
|
|
69
|
+
kind: 'clusters',
|
|
70
|
+
action: 'configureArchiving',
|
|
71
|
+
request: {
|
|
72
|
+
reviewedContext: reviewed.context,
|
|
73
|
+
uid: reviewed.uid,
|
|
74
|
+
facts: reviewed.facts,
|
|
75
|
+
params: {
|
|
76
|
+
objectStore: reviewed.facts.objectStore,
|
|
77
|
+
serverName: reviewed.facts.serverName,
|
|
78
|
+
acknowledgeArchive: acknowledged,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
onSuccess: () => {
|
|
84
|
+
showSuccess('Archive configuration saved. Check WAL uploads and a successful base backup next.')
|
|
85
|
+
onClose()
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
}}
|
|
90
|
+
title={reviewed ? 'Review archive attachment' : 'Choose existing backup storage'}
|
|
91
|
+
subject={{ kind: 'Cluster', namespace, name }}
|
|
92
|
+
context={context}
|
|
93
|
+
effect="Enable the Barman plugin for WAL archiving and base backups. The ObjectStore itself is unchanged."
|
|
94
|
+
disabledReason={
|
|
95
|
+
changed ? 'The context changed. Close this guide and start again in the current context.' : undefined
|
|
96
|
+
}
|
|
97
|
+
incompleteReason={
|
|
98
|
+
!reviewed && (!objectStore.trim() || !serverName.trim())
|
|
99
|
+
? 'Choose a store and archive server name'
|
|
100
|
+
: reviewed && !acknowledged
|
|
101
|
+
? 'Confirm this archive identity is reserved for this Cluster'
|
|
102
|
+
: undefined
|
|
103
|
+
}
|
|
104
|
+
guard={reviewed ? guard.node : undefined}
|
|
105
|
+
guardSatisfied={!upgrade && (!reviewed || guard.satisfied)}
|
|
106
|
+
confirmLabel={reviewed ? (reviewed.unchanged ? 'Keep configuration' : 'Enable archiving') : 'Review attachment'}
|
|
107
|
+
isLoading={preview.isPending || write.isPending}
|
|
108
|
+
result={upgrade && result?.state === 'error' ? undefined : result}
|
|
109
|
+
warnings={reviewed?.warnings}
|
|
110
|
+
writes={
|
|
111
|
+
reviewed
|
|
112
|
+
? [
|
|
113
|
+
{
|
|
114
|
+
summary: `patch Cluster ${namespace}/${name}`,
|
|
115
|
+
detail: `spec.plugins: enable ${String(reviewed.plugin.name)} with barmanObjectName=${reviewed.facts.objectStore}, serverName=${reviewed.facts.serverName}; preserve other plugins and parameters`,
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
: undefined
|
|
119
|
+
}
|
|
120
|
+
>
|
|
121
|
+
{upgrade && <RadarUpgradeNote requirement={upgrade} />}
|
|
122
|
+
{reviewed ? (
|
|
123
|
+
<div className="space-y-3">
|
|
124
|
+
<FactGrid>
|
|
125
|
+
<FactRow label="ObjectStore">{reviewed.facts.objectStore}</FactRow>
|
|
126
|
+
<FactRow label="Archive identity">{reviewed.facts.serverName}</FactRow>
|
|
127
|
+
<FactRow label="Destination">
|
|
128
|
+
<span className="break-all font-mono">{reviewed.destination}</span>
|
|
129
|
+
</FactRow>
|
|
130
|
+
{reviewed.endpoint && (
|
|
131
|
+
<FactRow label="Endpoint">
|
|
132
|
+
<span className="break-all font-mono">{reviewed.endpoint}</span>
|
|
133
|
+
</FactRow>
|
|
134
|
+
)}
|
|
135
|
+
</FactGrid>
|
|
136
|
+
<p className="text-xs text-theme-text-secondary">
|
|
137
|
+
Kubernetes accepted the server dry-run. This does not verify uploads or archive isolation. The selected
|
|
138
|
+
ObjectStore is re-read on confirmation; it is not locked against a concurrent change.
|
|
139
|
+
</p>
|
|
140
|
+
<label className="flex items-start gap-2 text-sm">
|
|
141
|
+
<input type="checkbox" checked={acknowledged} onChange={(event) => setAcknowledged(event.target.checked)} />
|
|
142
|
+
<span>I confirm this archive identity is reserved for {name}.</span>
|
|
143
|
+
</label>
|
|
144
|
+
</div>
|
|
145
|
+
) : (
|
|
146
|
+
<div className="space-y-3">
|
|
147
|
+
<label className="block">
|
|
148
|
+
<span className="text-xs font-medium text-theme-text-secondary">ObjectStore · namespace {namespace}</span>
|
|
149
|
+
<Input
|
|
150
|
+
aria-label="ObjectStore name"
|
|
151
|
+
list="cnpg-protection-stores"
|
|
152
|
+
value={objectStore}
|
|
153
|
+
disabled={!!current?.objectStore || preview.isPending}
|
|
154
|
+
onChange={(event) => setObjectStore(event.target.value)}
|
|
155
|
+
className={CNPG_FORM_FIELD}
|
|
156
|
+
/>
|
|
157
|
+
<datalist id="cnpg-protection-stores">
|
|
158
|
+
{stores.map((store) => (
|
|
159
|
+
<option key={store.metadata.name} value={store.metadata.name} />
|
|
160
|
+
))}
|
|
161
|
+
</datalist>
|
|
162
|
+
</label>
|
|
163
|
+
<label className="block">
|
|
164
|
+
<span className="text-xs font-medium text-theme-text-secondary">Archive server name</span>
|
|
165
|
+
<Input
|
|
166
|
+
aria-label="Archive server name"
|
|
167
|
+
value={serverName}
|
|
168
|
+
disabled={!!current?.objectStore || preview.isPending}
|
|
169
|
+
onChange={(event) => setServerName(event.target.value)}
|
|
170
|
+
className={CNPG_FORM_FIELD}
|
|
171
|
+
/>
|
|
172
|
+
</label>
|
|
173
|
+
<p className="text-xs text-theme-text-secondary">
|
|
174
|
+
This name separates this Cluster’s archive inside the storage destination. A restored Cluster must use a
|
|
175
|
+
different archive identity from its recovery source. Store names alone do not establish isolation.
|
|
176
|
+
</p>
|
|
177
|
+
<p className="text-xs text-theme-text-tertiary">
|
|
178
|
+
Enter an existing store directly if suggestions are unavailable. Creating stores, configuring credentials
|
|
179
|
+
and installing the plugin are separate prerequisites.
|
|
180
|
+
</p>
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
183
|
+
</ActionConfirmDialog>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import yaml from 'yaml'
|
|
3
|
+
import {
|
|
4
|
+
ActionConfirmDialog,
|
|
5
|
+
CNPGSchedulePreviewFacts,
|
|
6
|
+
ConfirmDialog,
|
|
7
|
+
FactGrid,
|
|
8
|
+
Input,
|
|
9
|
+
RadarUpgradeNote,
|
|
10
|
+
getRadarUpgradeRequirement,
|
|
11
|
+
useDebouncedValue,
|
|
12
|
+
} from '@skyhook-io/k8s-ui'
|
|
13
|
+
import { validateRFC1123Label } from '@skyhook-io/k8s-ui/utils/validators'
|
|
14
|
+
import { useCNPGDraftSchedulePreview } from '../../../api/cnpg-protection'
|
|
15
|
+
import { useConnection } from '../../../context/ConnectionContext'
|
|
16
|
+
import { CreateResourceDialog } from '../../shared/CreateResourceDialog'
|
|
17
|
+
import { CNPGScheduleInput } from './CNPGScheduleInput'
|
|
18
|
+
import { useToast } from '../../ui/Toast'
|
|
19
|
+
import { CNPG_FORM_FIELD } from '../formFields'
|
|
20
|
+
|
|
21
|
+
export function CNPGCreateScheduleDialog({
|
|
22
|
+
namespace,
|
|
23
|
+
cluster,
|
|
24
|
+
onClose,
|
|
25
|
+
onCreated,
|
|
26
|
+
}: {
|
|
27
|
+
namespace: string
|
|
28
|
+
cluster: string
|
|
29
|
+
onClose: () => void
|
|
30
|
+
onCreated: () => void
|
|
31
|
+
}) {
|
|
32
|
+
const { connection } = useConnection()
|
|
33
|
+
const [context] = useState(connection.context)
|
|
34
|
+
const [name, setName] = useState(`${cluster.slice(0, 56)}-backup`)
|
|
35
|
+
const [schedule, setSchedule] = useState('0 0 2 * * *')
|
|
36
|
+
const [immediate, setImmediate] = useState(false)
|
|
37
|
+
const [draft, setDraft] = useState<string | null>(null)
|
|
38
|
+
const [editing, setEditing] = useState(false)
|
|
39
|
+
const [replace, setReplace] = useState(false)
|
|
40
|
+
const debounced = useDebouncedValue(schedule.trim(), 300)
|
|
41
|
+
const preview = useCNPGDraftSchedulePreview(namespace, cluster, debounced, !!debounced)
|
|
42
|
+
const { showSuccess } = useToast()
|
|
43
|
+
const current =
|
|
44
|
+
debounced === schedule.trim() && preview.data?.schedule === debounced && !preview.isFetching
|
|
45
|
+
? preview.data
|
|
46
|
+
: undefined
|
|
47
|
+
const generated = () =>
|
|
48
|
+
yaml.stringify({
|
|
49
|
+
apiVersion: 'postgresql.cnpg.io/v1',
|
|
50
|
+
kind: 'ScheduledBackup',
|
|
51
|
+
metadata: { name: name.trim(), namespace },
|
|
52
|
+
spec: {
|
|
53
|
+
cluster: { name: cluster },
|
|
54
|
+
method: 'plugin',
|
|
55
|
+
pluginConfiguration: { name: 'barman-cloud.cloudnative-pg.io' },
|
|
56
|
+
schedule: schedule.trim(),
|
|
57
|
+
immediate,
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
const upgrade = getRadarUpgradeRequirement(preview.error)
|
|
61
|
+
const changed = context !== connection.context
|
|
62
|
+
if (changed)
|
|
63
|
+
return (
|
|
64
|
+
<ActionConfirmDialog
|
|
65
|
+
open
|
|
66
|
+
onClose={onClose}
|
|
67
|
+
onConfirm={() => {}}
|
|
68
|
+
title="Create backup schedule"
|
|
69
|
+
subject={{ kind: 'Cluster', namespace, name: cluster }}
|
|
70
|
+
context={context}
|
|
71
|
+
effect="The context changed."
|
|
72
|
+
confirmLabel="Review manifest"
|
|
73
|
+
disabledReason="Close this dialog and start again in the current context."
|
|
74
|
+
/>
|
|
75
|
+
)
|
|
76
|
+
if (replace)
|
|
77
|
+
return (
|
|
78
|
+
<ConfirmDialog
|
|
79
|
+
open
|
|
80
|
+
onClose={() => setReplace(false)}
|
|
81
|
+
onConfirm={() => {
|
|
82
|
+
setDraft(generated())
|
|
83
|
+
setEditing(true)
|
|
84
|
+
setReplace(false)
|
|
85
|
+
}}
|
|
86
|
+
title="Replace the current schedule YAML?"
|
|
87
|
+
message="This uses the setup choices below and discards the YAML you edited."
|
|
88
|
+
variant="warning"
|
|
89
|
+
showWarning={false}
|
|
90
|
+
confirmLabel="Replace YAML"
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
if (editing && draft !== null)
|
|
94
|
+
return (
|
|
95
|
+
<CreateResourceDialog
|
|
96
|
+
open
|
|
97
|
+
onClose={onClose}
|
|
98
|
+
onBack={(value) => {
|
|
99
|
+
setDraft(value)
|
|
100
|
+
setEditing(false)
|
|
101
|
+
}}
|
|
102
|
+
backLabel="Back to schedule setup"
|
|
103
|
+
initialYaml={draft}
|
|
104
|
+
initialMode="create"
|
|
105
|
+
lockMode
|
|
106
|
+
title="Create backup schedule"
|
|
107
|
+
onCreated={(result) => {
|
|
108
|
+
onCreated()
|
|
109
|
+
showSuccess(
|
|
110
|
+
`${result.kind} ${result.name} created. Check its settings and the next backup; creation alone does not prove protection.`,
|
|
111
|
+
)
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
)
|
|
115
|
+
return (
|
|
116
|
+
<ActionConfirmDialog
|
|
117
|
+
open
|
|
118
|
+
onClose={onClose}
|
|
119
|
+
onConfirm={() => {
|
|
120
|
+
if (draft !== null && draft !== generated()) {
|
|
121
|
+
setReplace(true)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
setDraft(generated())
|
|
125
|
+
setEditing(true)
|
|
126
|
+
}}
|
|
127
|
+
title="Create matching backup schedule"
|
|
128
|
+
subject={{ kind: 'Cluster', namespace, name: cluster }}
|
|
129
|
+
context={context}
|
|
130
|
+
effect="Create a separate ScheduledBackup using this Cluster’s Barman plugin, then review its exact manifest."
|
|
131
|
+
size="wide"
|
|
132
|
+
confirmLabel="Review manifest"
|
|
133
|
+
guardSatisfied={!upgrade}
|
|
134
|
+
disabledReason={preview.error && !upgrade ? `Timing could not be checked: ${preview.error.message}` : undefined}
|
|
135
|
+
incompleteReason={
|
|
136
|
+
!name.trim()
|
|
137
|
+
? 'Enter a schedule name'
|
|
138
|
+
: !validateRFC1123Label(name.trim()).valid
|
|
139
|
+
? 'Use a valid schedule name'
|
|
140
|
+
: !current
|
|
141
|
+
? 'Checking the schedule…'
|
|
142
|
+
: !current.valid
|
|
143
|
+
? 'Choose a schedule the operator can run'
|
|
144
|
+
: undefined
|
|
145
|
+
}
|
|
146
|
+
notes={['Each scheduled run creates a Backup. This does not change the Cluster or validate credentials.']}
|
|
147
|
+
>
|
|
148
|
+
{upgrade && <RadarUpgradeNote requirement={upgrade} />}
|
|
149
|
+
{draft !== null && (
|
|
150
|
+
<div className="space-y-2 text-xs text-theme-text-secondary">
|
|
151
|
+
<p>
|
|
152
|
+
The current YAML is retained. Returning to review keeps it; rebuilding from these fields requires
|
|
153
|
+
confirmation.
|
|
154
|
+
</p>
|
|
155
|
+
<button type="button" className="btn-secondary px-3 py-1.5" onClick={() => setEditing(true)}>
|
|
156
|
+
Continue editing current YAML
|
|
157
|
+
</button>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
<label className="block">
|
|
161
|
+
<span className="text-xs font-medium text-theme-text-secondary">Schedule name · namespace {namespace}</span>
|
|
162
|
+
<Input
|
|
163
|
+
aria-label="Schedule name"
|
|
164
|
+
value={name}
|
|
165
|
+
onChange={(event) => setName(event.target.value)}
|
|
166
|
+
className={CNPG_FORM_FIELD}
|
|
167
|
+
/>
|
|
168
|
+
</label>
|
|
169
|
+
<CNPGScheduleInput value={schedule} onChange={setSchedule} />
|
|
170
|
+
<label className="flex items-start gap-2 text-sm">
|
|
171
|
+
<input type="checkbox" checked={immediate} onChange={(event) => setImmediate(event.target.checked)} />
|
|
172
|
+
<span>
|
|
173
|
+
Also request a backup when the schedule is created.
|
|
174
|
+
<span className="mt-1 block text-xs text-theme-text-tertiary">
|
|
175
|
+
Off by default. Otherwise the first backup waits for its scheduled run.
|
|
176
|
+
</span>
|
|
177
|
+
</span>
|
|
178
|
+
</label>
|
|
179
|
+
{current && (
|
|
180
|
+
<FactGrid>
|
|
181
|
+
<CNPGSchedulePreviewFacts preview={current} />
|
|
182
|
+
</FactGrid>
|
|
183
|
+
)}
|
|
184
|
+
{preview.error && (
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
className="text-xs text-accent-text hover:underline"
|
|
188
|
+
onClick={() => void preview.refetch()}
|
|
189
|
+
disabled={preview.isFetching}
|
|
190
|
+
>
|
|
191
|
+
Retry timing check
|
|
192
|
+
</button>
|
|
193
|
+
)}
|
|
194
|
+
</ActionConfirmDialog>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { MemoryRouter } from 'react-router-dom'
|
|
4
|
+
import { beforeEach, expect, it, vi } from 'vitest'
|
|
5
|
+
import { CNPGProtectionSetup } from './CNPGProtectionSetup'
|
|
6
|
+
|
|
7
|
+
const state = vi.hoisted(() => ({
|
|
8
|
+
support: 'supported',
|
|
9
|
+
context: 'ctx',
|
|
10
|
+
cluster: {} as any,
|
|
11
|
+
schedules: [] as any[],
|
|
12
|
+
coverage: 'full',
|
|
13
|
+
wal: 'Not reported',
|
|
14
|
+
walState: 'unknown',
|
|
15
|
+
backup: 'No successful backup yet',
|
|
16
|
+
}))
|
|
17
|
+
vi.mock('../../../api/client', () => ({ useRadarFeature: () => ({ support: state.support }) }))
|
|
18
|
+
vi.mock('../../../context/ConnectionContext', () => ({
|
|
19
|
+
useConnection: () => ({ connection: { context: state.context } }),
|
|
20
|
+
}))
|
|
21
|
+
vi.mock('../useCNPGSidebarWorkspace', () => ({
|
|
22
|
+
useCNPGFleet: () => ({
|
|
23
|
+
query: {
|
|
24
|
+
data: {
|
|
25
|
+
objects: { objectStores: [], scheduledBackups: state.schedules },
|
|
26
|
+
coverage: { scheduledBackups: { state: state.coverage } },
|
|
27
|
+
},
|
|
28
|
+
refetch: vi.fn(),
|
|
29
|
+
},
|
|
30
|
+
fleet: {
|
|
31
|
+
rows: [
|
|
32
|
+
{
|
|
33
|
+
namespace: 'db',
|
|
34
|
+
name: 'pg',
|
|
35
|
+
cluster: state.cluster,
|
|
36
|
+
protection: { walArchiving: { text: state.wal, state: state.walState }, lastSuccessfulBackup: { text: state.backup } },
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
}))
|
|
42
|
+
vi.mock('../../../api/cnpg', () => ({
|
|
43
|
+
useCNPGOperator: () => ({
|
|
44
|
+
data: { components: [{ pluginName: 'barman-cloud.cloudnative-pg.io' }], coverage: { services: { state: 'full' } } },
|
|
45
|
+
}),
|
|
46
|
+
useCNPGClusterCapabilities: () => ({
|
|
47
|
+
data: { actions: { backup: { allowed: false, reason: 'You need create backups in namespace db' } } },
|
|
48
|
+
}),
|
|
49
|
+
}))
|
|
50
|
+
vi.mock('@skyhook-io/k8s-ui', async (importOriginal) => ({
|
|
51
|
+
...(await importOriginal<typeof import('@skyhook-io/k8s-ui')>()),
|
|
52
|
+
DialogPortal: ({ children }: any) => children,
|
|
53
|
+
}))
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
state.support = 'supported'
|
|
57
|
+
state.cluster = { metadata: { name: 'pg', namespace: 'db', uid: 'uid' }, spec: {} }
|
|
58
|
+
state.schedules = []
|
|
59
|
+
state.coverage = 'full'
|
|
60
|
+
state.walState = 'unknown'
|
|
61
|
+
state.wal = 'Not reported'
|
|
62
|
+
state.backup = 'No successful backup yet'
|
|
63
|
+
})
|
|
64
|
+
const text = () =>
|
|
65
|
+
new DOMParser().parseFromString(renderToStaticMarkup(
|
|
66
|
+
<MemoryRouter initialEntries={['/?protectionSetup=1']}>
|
|
67
|
+
<CNPGProtectionSetup namespace="db" name="pg" onInspect={vi.fn()} />
|
|
68
|
+
</MemoryRouter>,
|
|
69
|
+
), 'text/html').body.textContent!
|
|
70
|
+
|
|
71
|
+
it('presents the whole task without treating a declaration or an unread fact as protection', () => {
|
|
72
|
+
const html = text()
|
|
73
|
+
expect(html).toContain('1. Archive storage')
|
|
74
|
+
expect(html).toContain('2. Backup schedule')
|
|
75
|
+
expect(html).toContain('3. Verify protection')
|
|
76
|
+
expect(html).toContain('Attach the Barman archive destination first')
|
|
77
|
+
expect(html).toContain('You need create backups in namespace db')
|
|
78
|
+
expect(html).toContain('Not reported')
|
|
79
|
+
expect(html).not.toContain('Protection complete')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('makes a suspended matching schedule reachable rather than presenting active protection', () => {
|
|
83
|
+
state.cluster.spec.plugins = [
|
|
84
|
+
{ name: 'barman-cloud.cloudnative-pg.io', isWALArchiver: true, parameters: { barmanObjectName: 'store' } },
|
|
85
|
+
]
|
|
86
|
+
state.schedules = [
|
|
87
|
+
{
|
|
88
|
+
metadata: { name: 'nightly', namespace: 'db' },
|
|
89
|
+
spec: {
|
|
90
|
+
cluster: { name: 'pg' },
|
|
91
|
+
method: 'plugin',
|
|
92
|
+
pluginConfiguration: { name: 'barman-cloud.cloudnative-pg.io' },
|
|
93
|
+
schedule: '@daily',
|
|
94
|
+
suspend: true,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
]
|
|
98
|
+
expect(text()).toContain('suspended: open the schedule to resume it')
|
|
99
|
+
expect(text()).not.toContain('Create matching schedule…')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('does not interpret denied schedule inventory as an absent schedule', () => {
|
|
103
|
+
state.coverage = 'denied'
|
|
104
|
+
expect(text()).toContain('Inventory incomplete')
|
|
105
|
+
expect(text()).toContain('Schedules may exist outside the readable inventory')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('shows the upgrade path before offering setup writes against an older Radar', () => {
|
|
109
|
+
state.support = 'unsupported'
|
|
110
|
+
expect(text()).toContain('need a newer Radar')
|
|
111
|
+
expect(text()).not.toContain('Choose existing ObjectStore')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('puts failed uploads ahead of the setup milestones and links to the existing repair flow', () => {
|
|
115
|
+
state.wal = 'Failing'
|
|
116
|
+
state.walState = 'failing'
|
|
117
|
+
const html = new DOMParser().parseFromString(renderToStaticMarkup(<MemoryRouter initialEntries={['/?protectionSetup=1']}><CNPGProtectionSetup namespace="db" name="pg" onInspect={vi.fn()} onOpenArchivingRepair={vi.fn()} /></MemoryRouter>), 'text/html').body.textContent!
|
|
118
|
+
expect(html.indexOf('Repair WAL archiving first')).toBeLessThan(html.indexOf('1. Archive storage'))
|
|
119
|
+
expect(html).toContain('Open archiving repair')
|
|
120
|
+
expect(html).toContain('Uploads failing')
|
|
121
|
+
})
|