@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,452 @@
|
|
|
1
|
+
import { useEffect, useId, useState, type ReactNode } from 'react'
|
|
2
|
+
import { X } from 'lucide-react'
|
|
3
|
+
import { useLocation, useSearchParams } from 'react-router-dom'
|
|
4
|
+
import {
|
|
5
|
+
AlertBanner,
|
|
6
|
+
DialogPortal,
|
|
7
|
+
Badge,
|
|
8
|
+
FactGrid,
|
|
9
|
+
FactRow,
|
|
10
|
+
FactValue,
|
|
11
|
+
getCNPGClusterBarmanPlugin,
|
|
12
|
+
cnpgBackupDeclaration,
|
|
13
|
+
cnpgScheduleDestinationBlocker,
|
|
14
|
+
coverageReadable,
|
|
15
|
+
getRadarUpgradeRequirement,
|
|
16
|
+
RadarUpgradeNote,
|
|
17
|
+
type CNPGFleetRow,
|
|
18
|
+
} from '@skyhook-io/k8s-ui'
|
|
19
|
+
import { useCNPGClusterCapabilities, useCNPGOperator } from '../../../api/cnpg'
|
|
20
|
+
import { useRadarFeature } from '../../../api/client'
|
|
21
|
+
import { radarUpgradeRequirement } from '../../../api/radarFeatures'
|
|
22
|
+
import { useConnection } from '../../../context/ConnectionContext'
|
|
23
|
+
import { useCNPGFleet } from '../useCNPGSidebarWorkspace'
|
|
24
|
+
import { CNPGAttachArchiveDialog } from './CNPGAttachArchiveDialog'
|
|
25
|
+
import { CNPGCreateScheduleDialog } from './CNPGCreateScheduleDialog'
|
|
26
|
+
import { CNPGScheduleRepairDialog } from './CNPGScheduleRepairButton'
|
|
27
|
+
import { ClusterActionDialog } from '../actions/CNPGClusterActions'
|
|
28
|
+
import type { SelectedResource } from '../../../types'
|
|
29
|
+
import { cnpgResource } from '../shared'
|
|
30
|
+
|
|
31
|
+
export function CNPGProtectionSetup({
|
|
32
|
+
namespace,
|
|
33
|
+
name,
|
|
34
|
+
onInspect,
|
|
35
|
+
onOpenYaml,
|
|
36
|
+
onOpenOperator,
|
|
37
|
+
onOpenArchivingRepair,
|
|
38
|
+
}: {
|
|
39
|
+
namespace: string
|
|
40
|
+
name: string
|
|
41
|
+
onInspect: (resource: SelectedResource) => void
|
|
42
|
+
onOpenYaml?: () => void
|
|
43
|
+
onOpenOperator?: () => void
|
|
44
|
+
onOpenArchivingRepair?: () => void
|
|
45
|
+
}) {
|
|
46
|
+
const [params, setParams] = useSearchParams()
|
|
47
|
+
const location = useLocation()
|
|
48
|
+
const feature = useRadarFeature('cnpgProtectionSetup')
|
|
49
|
+
const [open, setOpen] = useState(() => params.get('protectionSetup') === '1')
|
|
50
|
+
const requested = params.get('protectionSetup') === '1'
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (requested) setOpen(true)
|
|
53
|
+
}, [requested])
|
|
54
|
+
const close = () => {
|
|
55
|
+
setOpen(false)
|
|
56
|
+
if (params.has('protectionSetup')) {
|
|
57
|
+
const next = new URLSearchParams(params)
|
|
58
|
+
next.delete('protectionSetup')
|
|
59
|
+
setParams(next, { replace: true, state: location.state })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return (
|
|
63
|
+
<>
|
|
64
|
+
<button type="button" onClick={() => setOpen(true)} className="btn-brand px-3 py-1.5 text-xs">
|
|
65
|
+
Set up backups…
|
|
66
|
+
</button>
|
|
67
|
+
{feature.support === 'unsupported' ? (
|
|
68
|
+
<DialogPortal
|
|
69
|
+
open={open}
|
|
70
|
+
onClose={close}
|
|
71
|
+
ariaLabel="Backup setup needs a newer Radar"
|
|
72
|
+
className="w-full max-w-lg p-5"
|
|
73
|
+
>
|
|
74
|
+
<RadarUpgradeNote requirement={radarUpgradeRequirement('cnpgProtectionSetup', {})} />
|
|
75
|
+
<button type="button" onClick={close} className="btn-secondary mt-4 px-3 py-1.5 text-sm">
|
|
76
|
+
Close
|
|
77
|
+
</button>
|
|
78
|
+
</DialogPortal>
|
|
79
|
+
) : (
|
|
80
|
+
open && (
|
|
81
|
+
<ProtectionGuide
|
|
82
|
+
namespace={namespace}
|
|
83
|
+
name={name}
|
|
84
|
+
onClose={close}
|
|
85
|
+
onInspect={onInspect}
|
|
86
|
+
onOpenYaml={onOpenYaml}
|
|
87
|
+
onOpenOperator={onOpenOperator}
|
|
88
|
+
onOpenArchivingRepair={onOpenArchivingRepair}
|
|
89
|
+
/>
|
|
90
|
+
)
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function Milestone({
|
|
97
|
+
number,
|
|
98
|
+
title,
|
|
99
|
+
state,
|
|
100
|
+
children,
|
|
101
|
+
}: {
|
|
102
|
+
number: number
|
|
103
|
+
title: string
|
|
104
|
+
state: string
|
|
105
|
+
children: ReactNode
|
|
106
|
+
}) {
|
|
107
|
+
return (
|
|
108
|
+
<section className="space-y-2 rounded-lg border border-theme-border bg-theme-surface p-3">
|
|
109
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
110
|
+
<h4 className="font-medium">
|
|
111
|
+
{number}. {title}
|
|
112
|
+
</h4>
|
|
113
|
+
<Badge severity="neutral" size="sm">
|
|
114
|
+
{state}
|
|
115
|
+
</Badge>
|
|
116
|
+
</div>
|
|
117
|
+
{children}
|
|
118
|
+
</section>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function ProtectionGuide({
|
|
123
|
+
namespace,
|
|
124
|
+
name,
|
|
125
|
+
onClose,
|
|
126
|
+
onInspect,
|
|
127
|
+
onOpenYaml,
|
|
128
|
+
onOpenOperator,
|
|
129
|
+
onOpenArchivingRepair,
|
|
130
|
+
}: {
|
|
131
|
+
namespace: string
|
|
132
|
+
name: string
|
|
133
|
+
onClose: () => void
|
|
134
|
+
onInspect: (resource: SelectedResource) => void
|
|
135
|
+
onOpenYaml?: () => void
|
|
136
|
+
onOpenOperator?: () => void
|
|
137
|
+
onOpenArchivingRepair?: () => void
|
|
138
|
+
}) {
|
|
139
|
+
const titleId = useId()
|
|
140
|
+
const { connection } = useConnection()
|
|
141
|
+
const [context] = useState(connection.context)
|
|
142
|
+
const { query, fleet } = useCNPGFleet([namespace])
|
|
143
|
+
const operator = useCNPGOperator()
|
|
144
|
+
const caps = useCNPGClusterCapabilities(namespace, name)
|
|
145
|
+
const [step, setStep] = useState<'attach' | 'schedule' | 'backup' | null>(null)
|
|
146
|
+
const [repair, setRepair] = useState<string | null>(null)
|
|
147
|
+
const data = query.data
|
|
148
|
+
const row: CNPGFleetRow | undefined = fleet?.rows.find((r) => r.namespace === namespace && r.name === name)
|
|
149
|
+
const cluster = row?.cluster
|
|
150
|
+
const plugin = cluster ? getCNPGClusterBarmanPlugin(cluster) : null
|
|
151
|
+
const attached = !!plugin?.barmanObjectName && plugin.isWALArchiver
|
|
152
|
+
const declaration = cluster ? cnpgBackupDeclaration(cluster) : null
|
|
153
|
+
const unsupported =
|
|
154
|
+
!!declaration &&
|
|
155
|
+
(declaration.inTreeConfigured ||
|
|
156
|
+
declaration.snapshotsConfigured ||
|
|
157
|
+
declaration.plugins.some((p) => p.enabled && p.isWALArchiver && p.name !== 'barman-cloud.cloudnative-pg.io'))
|
|
158
|
+
const pluginVisible = operator.data?.components.some(
|
|
159
|
+
(component) => component.pluginName === 'barman-cloud.cloudnative-pg.io',
|
|
160
|
+
)
|
|
161
|
+
const pluginKnownAbsent = !pluginVisible && operator.data?.coverage.services.state === 'full'
|
|
162
|
+
const stores = (data?.objects.objectStores ?? []).filter((store) => store.metadata.namespace === namespace)
|
|
163
|
+
const schedules = (data?.objects.scheduledBackups ?? []).filter(
|
|
164
|
+
(schedule) => schedule.metadata.namespace === namespace && schedule.spec?.cluster?.name === name,
|
|
165
|
+
)
|
|
166
|
+
const scheduleCoverage = data?.coverage.scheduledBackups
|
|
167
|
+
const schedulesReadable = scheduleCoverage && coverageReadable(scheduleCoverage, namespace)
|
|
168
|
+
const matching = schedules.filter(
|
|
169
|
+
(schedule) =>
|
|
170
|
+
!cnpgScheduleDestinationBlocker(schedule, [cluster]) &&
|
|
171
|
+
schedule.spec?.method === 'plugin' &&
|
|
172
|
+
schedule.spec?.pluginConfiguration?.name === 'barman-cloud.cloudnative-pg.io',
|
|
173
|
+
)
|
|
174
|
+
const repairable = schedules.filter(
|
|
175
|
+
(schedule) =>
|
|
176
|
+
cnpgScheduleDestinationBlocker(schedule, [cluster]) &&
|
|
177
|
+
(schedule.spec?.method || 'barmanObjectStore') !== 'volumeSnapshot',
|
|
178
|
+
)
|
|
179
|
+
const changed = connection.context !== context
|
|
180
|
+
const upgrade = getRadarUpgradeRequirement(query.error)
|
|
181
|
+
if (step === 'attach' && cluster && !changed)
|
|
182
|
+
return (
|
|
183
|
+
<CNPGAttachArchiveDialog
|
|
184
|
+
namespace={namespace}
|
|
185
|
+
name={name}
|
|
186
|
+
uid={cluster.metadata.uid}
|
|
187
|
+
stores={stores}
|
|
188
|
+
current={
|
|
189
|
+
plugin?.barmanObjectName
|
|
190
|
+
? { objectStore: plugin.barmanObjectName, serverName: plugin.serverName || name }
|
|
191
|
+
: undefined
|
|
192
|
+
}
|
|
193
|
+
onClose={() => setStep(null)}
|
|
194
|
+
/>
|
|
195
|
+
)
|
|
196
|
+
if (step === 'schedule' && !changed)
|
|
197
|
+
return (
|
|
198
|
+
<CNPGCreateScheduleDialog
|
|
199
|
+
namespace={namespace}
|
|
200
|
+
cluster={name}
|
|
201
|
+
onClose={() => setStep(null)}
|
|
202
|
+
onCreated={() => void query.refetch()}
|
|
203
|
+
/>
|
|
204
|
+
)
|
|
205
|
+
if (repair && !changed)
|
|
206
|
+
return <CNPGScheduleRepairDialog namespace={namespace} name={repair} onClose={() => setRepair(null)} />
|
|
207
|
+
if (step === 'backup' && caps.data && !changed)
|
|
208
|
+
return (
|
|
209
|
+
<ClusterActionDialog
|
|
210
|
+
kind="backup"
|
|
211
|
+
caps={caps.data}
|
|
212
|
+
namespace={namespace}
|
|
213
|
+
name={name}
|
|
214
|
+
onClose={() => setStep(null)}
|
|
215
|
+
/>
|
|
216
|
+
)
|
|
217
|
+
return (
|
|
218
|
+
<DialogPortal open onClose={onClose} ariaLabelledBy={titleId} className="w-full max-w-4xl">
|
|
219
|
+
<div className="flex items-start gap-3 border-b border-theme-border p-4">
|
|
220
|
+
<div className="min-w-0 flex-1">
|
|
221
|
+
<h3 id={titleId} className="text-lg font-semibold text-theme-text-primary">
|
|
222
|
+
Set up backups for {name}
|
|
223
|
+
</h3>
|
|
224
|
+
<p className="mt-0.5 text-xs text-theme-text-tertiary">
|
|
225
|
+
Cluster{' '}
|
|
226
|
+
<span className="font-mono">
|
|
227
|
+
{namespace}/{name}
|
|
228
|
+
</span>{' '}
|
|
229
|
+
· context <span className="font-mono">{context}</span>
|
|
230
|
+
</p>
|
|
231
|
+
</div>
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
onClick={onClose}
|
|
235
|
+
aria-label="Close"
|
|
236
|
+
className="rounded p-1 text-theme-text-secondary hover:bg-theme-hover"
|
|
237
|
+
>
|
|
238
|
+
<X className="h-5 w-5" />
|
|
239
|
+
</button>
|
|
240
|
+
</div>
|
|
241
|
+
<div className="max-h-[70vh] space-y-4 overflow-y-auto p-4 text-sm text-theme-text-primary">
|
|
242
|
+
<p>
|
|
243
|
+
Connect existing storage, establish a matching schedule, then verify the results. Each change has its own
|
|
244
|
+
review; saving configuration alone does not prove protection.
|
|
245
|
+
</p>
|
|
246
|
+
{changed && (
|
|
247
|
+
<AlertBanner
|
|
248
|
+
variant="info"
|
|
249
|
+
title="Context changed"
|
|
250
|
+
message="Close this guide and start again in the current context."
|
|
251
|
+
/>
|
|
252
|
+
)}
|
|
253
|
+
{!row ? (
|
|
254
|
+
<div>
|
|
255
|
+
{upgrade ? (
|
|
256
|
+
<RadarUpgradeNote requirement={upgrade} />
|
|
257
|
+
) : (
|
|
258
|
+
<>
|
|
259
|
+
<p>
|
|
260
|
+
{query.error
|
|
261
|
+
? query.error.message
|
|
262
|
+
: query.isLoading
|
|
263
|
+
? 'Reading Cluster backup configuration…'
|
|
264
|
+
: 'This Cluster is not readable in the workspace. Check its identity and access before setup.'}
|
|
265
|
+
</p>
|
|
266
|
+
<button
|
|
267
|
+
type="button"
|
|
268
|
+
className="text-accent-text hover:underline"
|
|
269
|
+
onClick={() => void query.refetch()}
|
|
270
|
+
disabled={query.isFetching}
|
|
271
|
+
>
|
|
272
|
+
Retry
|
|
273
|
+
</button>
|
|
274
|
+
</>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
) : (
|
|
278
|
+
<div className="space-y-3">
|
|
279
|
+
{row.protection.walArchiving.state === 'failing' && <AlertBanner variant="error" title="Repair WAL archiving first">
|
|
280
|
+
<p>Uploads are failing. Repair the archive connection before relying on a schedule or taking a new base backup.</p>
|
|
281
|
+
{onOpenArchivingRepair && <button type="button" className="mt-2 text-sm text-accent-text hover:underline" onClick={() => { onClose(); onOpenArchivingRepair() }}>Open archiving repair →</button>}
|
|
282
|
+
</AlertBanner>}
|
|
283
|
+
<Milestone number={1} title="Archive storage" state={row.protection.walArchiving.state === 'failing' ? 'Uploads failing' : attached ? 'Declared' : 'Not attached'}>
|
|
284
|
+
{attached ? (
|
|
285
|
+
<p className="text-sm text-theme-text-secondary">
|
|
286
|
+
ObjectStore <span className="font-mono">{plugin.barmanObjectName}</span> · archive server{' '}
|
|
287
|
+
<span className="font-mono">{plugin.serverName}</span>. Uploads still need verification.
|
|
288
|
+
</p>
|
|
289
|
+
) : (
|
|
290
|
+
<>
|
|
291
|
+
<p className="text-sm text-theme-text-secondary">
|
|
292
|
+
Attach an existing ObjectStore in {namespace} and reserve a distinct archive identity for this
|
|
293
|
+
Cluster.
|
|
294
|
+
</p>
|
|
295
|
+
<button
|
|
296
|
+
type="button"
|
|
297
|
+
className="btn-secondary px-3 py-1.5 text-xs"
|
|
298
|
+
onClick={() => setStep('attach')}
|
|
299
|
+
disabled={unsupported || pluginKnownAbsent || changed}
|
|
300
|
+
>
|
|
301
|
+
Choose existing ObjectStore…
|
|
302
|
+
</button>
|
|
303
|
+
</>
|
|
304
|
+
)}
|
|
305
|
+
{unsupported && (
|
|
306
|
+
<p className="text-xs text-theme-text-secondary">
|
|
307
|
+
This configuration needs a backup-method migration. Review it in Cluster YAML; the guide preserves
|
|
308
|
+
existing in-tree, snapshot and other-plugin arrangements.
|
|
309
|
+
</p>
|
|
310
|
+
)}
|
|
311
|
+
{pluginKnownAbsent && (
|
|
312
|
+
<p className="text-xs text-theme-text-secondary">
|
|
313
|
+
No Barman plugin Service was found in the readable operator scope. Install it before attaching
|
|
314
|
+
storage.
|
|
315
|
+
</p>
|
|
316
|
+
)}
|
|
317
|
+
{!operator.data && (
|
|
318
|
+
<p className="text-xs text-theme-text-tertiary">
|
|
319
|
+
Plugin installation could not yet be established. Server review checks the declaration; it cannot
|
|
320
|
+
verify upload readiness.
|
|
321
|
+
</p>
|
|
322
|
+
)}
|
|
323
|
+
{onOpenOperator && (
|
|
324
|
+
<button
|
|
325
|
+
type="button"
|
|
326
|
+
className="text-xs text-accent-text hover:underline"
|
|
327
|
+
onClick={() => {
|
|
328
|
+
onClose()
|
|
329
|
+
onOpenOperator()
|
|
330
|
+
}}
|
|
331
|
+
>
|
|
332
|
+
Operator & plugin prerequisites →
|
|
333
|
+
</button>
|
|
334
|
+
)}
|
|
335
|
+
{onOpenYaml && (
|
|
336
|
+
<button
|
|
337
|
+
type="button"
|
|
338
|
+
className="ml-3 text-xs text-accent-text hover:underline"
|
|
339
|
+
onClick={() => {
|
|
340
|
+
onClose()
|
|
341
|
+
onOpenYaml()
|
|
342
|
+
}}
|
|
343
|
+
>
|
|
344
|
+
Cluster YAML →
|
|
345
|
+
</button>
|
|
346
|
+
)}
|
|
347
|
+
</Milestone>
|
|
348
|
+
<Milestone
|
|
349
|
+
number={2}
|
|
350
|
+
title="Backup schedule"
|
|
351
|
+
state={
|
|
352
|
+
!schedulesReadable ? 'Inventory incomplete' : matching.length ? 'Matching declaration' : 'Needs setup'
|
|
353
|
+
}
|
|
354
|
+
>
|
|
355
|
+
{matching.map((schedule) => (
|
|
356
|
+
<p key={schedule.metadata.name} className="text-sm text-theme-text-secondary">
|
|
357
|
+
<button
|
|
358
|
+
type="button"
|
|
359
|
+
className="text-accent-text hover:underline"
|
|
360
|
+
onClick={() => {
|
|
361
|
+
onClose()
|
|
362
|
+
onInspect(cnpgResource('ScheduledBackup', namespace, schedule.metadata.name))
|
|
363
|
+
}}
|
|
364
|
+
>
|
|
365
|
+
{schedule.metadata.name} →
|
|
366
|
+
</button>{' '}
|
|
367
|
+
· {schedule.spec.schedule}
|
|
368
|
+
{schedule.spec.suspend ? ' · suspended: open the schedule to resume it' : ''}
|
|
369
|
+
</p>
|
|
370
|
+
))}
|
|
371
|
+
{repairable.map((schedule) => (
|
|
372
|
+
<div key={schedule.metadata.name} className="flex flex-wrap items-center gap-2 text-sm">
|
|
373
|
+
<span>{schedule.metadata.name} cannot use the declared destination.</span>
|
|
374
|
+
<button
|
|
375
|
+
type="button"
|
|
376
|
+
className="btn-secondary px-3 py-1.5 text-xs"
|
|
377
|
+
onClick={() => setRepair(schedule.metadata.name)}
|
|
378
|
+
disabled={!attached || changed}
|
|
379
|
+
>
|
|
380
|
+
Review method repair…
|
|
381
|
+
</button>
|
|
382
|
+
</div>
|
|
383
|
+
))}
|
|
384
|
+
{!matching.length && (
|
|
385
|
+
<button
|
|
386
|
+
type="button"
|
|
387
|
+
className="btn-secondary px-3 py-1.5 text-xs"
|
|
388
|
+
onClick={() => setStep('schedule')}
|
|
389
|
+
disabled={!attached || changed}
|
|
390
|
+
>
|
|
391
|
+
Create matching schedule…
|
|
392
|
+
</button>
|
|
393
|
+
)}
|
|
394
|
+
{!attached && (
|
|
395
|
+
<p className="text-xs text-theme-text-tertiary">
|
|
396
|
+
Attach the Barman archive destination first. Existing schedules are left unchanged.
|
|
397
|
+
</p>
|
|
398
|
+
)}
|
|
399
|
+
{!schedulesReadable && (
|
|
400
|
+
<p className="text-xs text-theme-text-tertiary">
|
|
401
|
+
Schedules may exist outside the readable inventory. Check before creating another; strict creation
|
|
402
|
+
will not replace an existing object.
|
|
403
|
+
</p>
|
|
404
|
+
)}
|
|
405
|
+
</Milestone>
|
|
406
|
+
<Milestone number={3} title="Verify protection" state="Observed evidence">
|
|
407
|
+
<FactGrid>
|
|
408
|
+
<FactRow label="WAL archiving">
|
|
409
|
+
<FactValue fact={row.protection.walArchiving} />
|
|
410
|
+
</FactRow>
|
|
411
|
+
<FactRow label="Last successful backup">
|
|
412
|
+
<FactValue fact={row.protection.lastSuccessfulBackup} />
|
|
413
|
+
</FactRow>
|
|
414
|
+
</FactGrid>
|
|
415
|
+
<p className="text-xs text-theme-text-secondary">
|
|
416
|
+
Confirm uploads resumed, then complete a new base backup for this configuration. Older successful
|
|
417
|
+
backups do not prove a new attachment works. Restore validation is a separate task.
|
|
418
|
+
</p>
|
|
419
|
+
<button
|
|
420
|
+
type="button"
|
|
421
|
+
className="btn-secondary px-3 py-1.5 text-xs"
|
|
422
|
+
onClick={() => setStep('backup')}
|
|
423
|
+
disabled={!caps.data?.actions.backup.allowed || changed}
|
|
424
|
+
>
|
|
425
|
+
Back up now…
|
|
426
|
+
</button>
|
|
427
|
+
{!caps.data?.actions.backup.allowed && (
|
|
428
|
+
<p className="text-xs text-theme-text-tertiary">
|
|
429
|
+
{caps.data?.actions.backup.reason ||
|
|
430
|
+
(caps.error ? caps.error.message : 'Checking backup permission and Cluster state…')}
|
|
431
|
+
</p>
|
|
432
|
+
)}
|
|
433
|
+
<button
|
|
434
|
+
type="button"
|
|
435
|
+
className="ml-3 text-xs text-accent-text hover:underline"
|
|
436
|
+
onClick={() => void query.refetch()}
|
|
437
|
+
disabled={query.isFetching}
|
|
438
|
+
>
|
|
439
|
+
Refresh evidence
|
|
440
|
+
</button>
|
|
441
|
+
</Milestone>
|
|
442
|
+
</div>
|
|
443
|
+
)}
|
|
444
|
+
</div>
|
|
445
|
+
<div className="flex justify-end border-t border-theme-border p-4">
|
|
446
|
+
<button type="button" onClick={onClose} className="btn-secondary px-4 py-2 text-sm">
|
|
447
|
+
Close guide
|
|
448
|
+
</button>
|
|
449
|
+
</div>
|
|
450
|
+
</DialogPortal>
|
|
451
|
+
)
|
|
452
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useId, useState } from 'react'
|
|
2
|
+
import { Input } from '@skyhook-io/k8s-ui'
|
|
3
|
+
import { scheduleExpression, simpleSchedule, type SimpleSchedule } from './scheduleModel'
|
|
4
|
+
import { CNPG_FORM_FIELD as FIELD } from '../formFields'
|
|
5
|
+
|
|
6
|
+
export function CNPGScheduleInput({ value, onChange }: { value: string; onChange: (value: string) => void }) {
|
|
7
|
+
const id = useId()
|
|
8
|
+
const simple = simpleSchedule(value)
|
|
9
|
+
const [advanced, setAdvanced] = useState(!simple)
|
|
10
|
+
const selected = advanced || !simple ? 'advanced' : simple.frequency
|
|
11
|
+
const set = (next: Partial<SimpleSchedule>) =>
|
|
12
|
+
onChange(scheduleExpression({ frequency: 'daily', time: '02:00', day: '1', ...simple, ...next }))
|
|
13
|
+
return (
|
|
14
|
+
<div className="space-y-2">
|
|
15
|
+
<div className="grid gap-3 sm:grid-cols-3">
|
|
16
|
+
<label className="block">
|
|
17
|
+
<span className="text-xs font-medium text-theme-text-secondary">Frequency</span>
|
|
18
|
+
<select
|
|
19
|
+
id={`${id}-frequency`}
|
|
20
|
+
aria-label="Backup frequency"
|
|
21
|
+
className={FIELD}
|
|
22
|
+
value={selected}
|
|
23
|
+
onChange={(event) => {
|
|
24
|
+
const frequency = event.target.value
|
|
25
|
+
setAdvanced(frequency === 'advanced')
|
|
26
|
+
if (frequency !== 'advanced')
|
|
27
|
+
set({
|
|
28
|
+
frequency: frequency as SimpleSchedule['frequency'],
|
|
29
|
+
day: frequency === 'weekly' ? '1' : simple?.frequency === 'monthly' ? simple.day : '1',
|
|
30
|
+
})
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<option value="daily">Daily</option>
|
|
34
|
+
<option value="weekly">Weekly</option>
|
|
35
|
+
<option value="monthly">Monthly</option>
|
|
36
|
+
<option value="advanced">Advanced cron</option>
|
|
37
|
+
</select>
|
|
38
|
+
</label>
|
|
39
|
+
{selected !== 'advanced' && (
|
|
40
|
+
<label className="block">
|
|
41
|
+
<span className="text-xs font-medium text-theme-text-secondary">Time · operator clock</span>
|
|
42
|
+
<Input
|
|
43
|
+
type="time"
|
|
44
|
+
className={FIELD}
|
|
45
|
+
value={simple?.time ?? '02:00'}
|
|
46
|
+
onChange={(event) => {
|
|
47
|
+
if (event.target.value) set({ time: event.target.value })
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
</label>
|
|
51
|
+
)}
|
|
52
|
+
{selected === 'weekly' && (
|
|
53
|
+
<label className="block">
|
|
54
|
+
<span className="text-xs font-medium text-theme-text-secondary">Day</span>
|
|
55
|
+
<select
|
|
56
|
+
aria-label="Backup weekday"
|
|
57
|
+
className={FIELD}
|
|
58
|
+
value={simple?.day}
|
|
59
|
+
onChange={(event) => set({ day: event.target.value })}
|
|
60
|
+
>
|
|
61
|
+
{['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].map((day, index) => (
|
|
62
|
+
<option key={day} value={index}>
|
|
63
|
+
{day}
|
|
64
|
+
</option>
|
|
65
|
+
))}
|
|
66
|
+
</select>
|
|
67
|
+
</label>
|
|
68
|
+
)}
|
|
69
|
+
{selected === 'monthly' && (
|
|
70
|
+
<label className="block">
|
|
71
|
+
<span className="text-xs font-medium text-theme-text-secondary">Day of month</span>
|
|
72
|
+
<select
|
|
73
|
+
aria-label="Backup day of month"
|
|
74
|
+
className={FIELD}
|
|
75
|
+
value={simple?.day}
|
|
76
|
+
onChange={(event) => set({ day: event.target.value })}
|
|
77
|
+
>
|
|
78
|
+
{Array.from({ length: 31 }, (_, index) => (
|
|
79
|
+
<option key={index} value={index + 1}>
|
|
80
|
+
{index + 1}
|
|
81
|
+
</option>
|
|
82
|
+
))}
|
|
83
|
+
</select>
|
|
84
|
+
</label>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
{selected === 'advanced' ? (
|
|
88
|
+
<label className="block">
|
|
89
|
+
<span className="text-xs font-medium text-theme-text-secondary">
|
|
90
|
+
Schedule · seconds first, or a descriptor such as @daily
|
|
91
|
+
</span>
|
|
92
|
+
<Input
|
|
93
|
+
id="cnpg-schedule-input"
|
|
94
|
+
className={`${FIELD} font-mono`}
|
|
95
|
+
value={value}
|
|
96
|
+
onChange={(event) => onChange(event.target.value)}
|
|
97
|
+
spellCheck={false}
|
|
98
|
+
autoComplete="off"
|
|
99
|
+
/>
|
|
100
|
+
</label>
|
|
101
|
+
) : (
|
|
102
|
+
<div className="font-mono text-xs text-theme-text-tertiary">{value}</div>
|
|
103
|
+
)}
|
|
104
|
+
{selected === 'monthly' && Number(simple?.day) > 28 && (
|
|
105
|
+
<p className="text-xs text-theme-text-secondary">
|
|
106
|
+
Months without day {simple?.day} are skipped; no backup is scheduled for that month.
|
|
107
|
+
</p>
|
|
108
|
+
)}
|
|
109
|
+
<p className="text-xs text-theme-text-tertiary">
|
|
110
|
+
Times use the operator’s clock, not your browser’s. The server preview below states which clock is known or
|
|
111
|
+
assumed.
|
|
112
|
+
</p>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ActionConfirmDialog,
|
|
4
|
+
FactGrid,
|
|
5
|
+
FactRow,
|
|
6
|
+
RadarUpgradeNote,
|
|
7
|
+
getRadarUpgradeRequirement,
|
|
8
|
+
} from '@skyhook-io/k8s-ui'
|
|
9
|
+
import { useCNPGProtectionAction, useCNPGScheduleMethodPreview } from '../../../api/cnpg-protection'
|
|
10
|
+
import { useConnection } from '../../../context/ConnectionContext'
|
|
11
|
+
import { actionDialogResult } from '../../../api/actions'
|
|
12
|
+
import { useCNPGWriteGuard } from '../actions/useCNPGWriteGuard'
|
|
13
|
+
import { useToast } from '../../ui/Toast'
|
|
14
|
+
|
|
15
|
+
export function CNPGScheduleRepairButton({ namespace, name }: { namespace: string; name: string }) {
|
|
16
|
+
const [open, setOpen] = useState(false)
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<button type="button" className="btn-secondary px-3 py-1.5 text-xs" onClick={() => setOpen(true)}>
|
|
20
|
+
Match {name} to the Barman plugin…
|
|
21
|
+
</button>
|
|
22
|
+
{open && <CNPGScheduleRepairDialog namespace={namespace} name={name} onClose={() => setOpen(false)} />}
|
|
23
|
+
</>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function CNPGScheduleRepairDialog({
|
|
28
|
+
namespace,
|
|
29
|
+
name,
|
|
30
|
+
onClose,
|
|
31
|
+
}: {
|
|
32
|
+
namespace: string
|
|
33
|
+
name: string
|
|
34
|
+
onClose: () => void
|
|
35
|
+
}) {
|
|
36
|
+
const preview = useCNPGScheduleMethodPreview(namespace, name)
|
|
37
|
+
const mutation = useCNPGProtectionAction('scheduledbackups', namespace, name, preview.data?.context)
|
|
38
|
+
const { connection } = useConnection()
|
|
39
|
+
const [context] = useState(connection.context)
|
|
40
|
+
const { showSuccess } = useToast()
|
|
41
|
+
const guard = useCNPGWriteGuard({
|
|
42
|
+
namespace,
|
|
43
|
+
name,
|
|
44
|
+
targetKind: 'ScheduledBackup',
|
|
45
|
+
scope: { kind: 'spec', paths: ['spec.method', 'spec.pluginConfiguration.name'] },
|
|
46
|
+
})
|
|
47
|
+
const data = preview.data
|
|
48
|
+
const changed = connection.context !== context || (data && data.context !== context)
|
|
49
|
+
const upgrade = getRadarUpgradeRequirement(preview.error)
|
|
50
|
+
return (
|
|
51
|
+
<ActionConfirmDialog
|
|
52
|
+
open
|
|
53
|
+
onClose={onClose}
|
|
54
|
+
onConfirm={() => {
|
|
55
|
+
if (!data) return
|
|
56
|
+
mutation.mutate(
|
|
57
|
+
{
|
|
58
|
+
kind: 'scheduledbackups',
|
|
59
|
+
action: 'repairMethod',
|
|
60
|
+
request: { reviewedContext: data.context, uid: data.uid, facts: data.facts },
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
onSuccess: () => {
|
|
64
|
+
showSuccess(`${name} now uses the Cluster’s Barman plugin. Verify its next backup.`)
|
|
65
|
+
onClose()
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
}}
|
|
70
|
+
title="Repair backup schedule method"
|
|
71
|
+
subject={{ kind: 'ScheduledBackup', namespace, name }}
|
|
72
|
+
context={context}
|
|
73
|
+
effect="Use the Cluster’s existing Barman plugin for this schedule. Cluster target, timing, immediate/online choices and other settings are preserved."
|
|
74
|
+
disabledReason={
|
|
75
|
+
changed
|
|
76
|
+
? 'The context changed. Close and review this repair again.'
|
|
77
|
+
: data?.unchanged
|
|
78
|
+
? 'The schedule already matches the Barman plugin.'
|
|
79
|
+
: preview.error
|
|
80
|
+
? preview.error.message
|
|
81
|
+
: undefined
|
|
82
|
+
}
|
|
83
|
+
incompleteReason={!data && !preview.error ? 'Checking the method and server dry-run…' : undefined}
|
|
84
|
+
guard={data ? guard.node : undefined}
|
|
85
|
+
guardSatisfied={!!data && guard.satisfied}
|
|
86
|
+
confirmLabel="Repair method"
|
|
87
|
+
isLoading={mutation.isPending}
|
|
88
|
+
result={actionDialogResult(mutation.outcomeError ?? mutation.error, { unknown: "An earlier write to this ScheduledBackup may have taken effect" })}
|
|
89
|
+
writes={
|
|
90
|
+
data
|
|
91
|
+
? [
|
|
92
|
+
{
|
|
93
|
+
summary: `patch ScheduledBackup ${namespace}/${name}`,
|
|
94
|
+
detail: `spec.method = plugin; spec.pluginConfiguration.name = barman-cloud.cloudnative-pg.io`,
|
|
95
|
+
},
|
|
96
|
+
]
|
|
97
|
+
: undefined
|
|
98
|
+
}
|
|
99
|
+
>
|
|
100
|
+
{upgrade ? (
|
|
101
|
+
<RadarUpgradeNote requirement={upgrade} />
|
|
102
|
+
) : data ? (
|
|
103
|
+
<FactGrid>
|
|
104
|
+
<FactRow label="Cluster">{data.cluster}</FactRow>
|
|
105
|
+
<FactRow label="Current method">{data.previousMethod}</FactRow>
|
|
106
|
+
<FactRow label="After repair">plugin · barman-cloud.cloudnative-pg.io</FactRow>
|
|
107
|
+
</FactGrid>
|
|
108
|
+
) : preview.error ? (
|
|
109
|
+
<button
|
|
110
|
+
type="button"
|
|
111
|
+
onClick={() => void preview.refetch()}
|
|
112
|
+
disabled={preview.isFetching}
|
|
113
|
+
className="text-accent-text hover:underline"
|
|
114
|
+
>
|
|
115
|
+
Retry review
|
|
116
|
+
</button>
|
|
117
|
+
) : null}
|
|
118
|
+
</ActionConfirmDialog>
|
|
119
|
+
)
|
|
120
|
+
}
|