@skyhook-io/radar-app 1.16.3 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/App.tsx +138 -93
- package/src/RadarApp.tsx +27 -17
- package/src/api/actions.errorCode.test.ts +40 -0
- package/src/api/actions.test.ts +48 -0
- package/src/api/actions.ts +79 -0
- package/src/api/apiResources.navigation.test.tsx +232 -0
- package/src/api/apiResources.ts +46 -3
- package/src/api/client.goneResource.test.ts +49 -0
- package/src/api/client.helmSource.test.ts +39 -0
- package/src/api/client.namespaceSwitch.hook.test.tsx +54 -0
- package/src/api/client.namespaceSwitch.test.ts +107 -0
- package/src/api/client.ts +381 -47
- package/src/api/cnpg-ha.live.test.ts +140 -0
- package/src/api/cnpg-ha.test.ts +78 -0
- package/src/api/cnpg-ha.ts +19 -0
- package/src/api/cnpg-history.ts +123 -0
- package/src/api/cnpg-inspect.ts +102 -0
- package/src/api/cnpg-protection.ts +112 -0
- package/src/api/cnpg-recovery.test.tsx +60 -0
- package/src/api/cnpg-recovery.ts +262 -0
- package/src/api/cnpg-sessions.ts +175 -0
- package/src/api/cnpg-storage.ts +138 -0
- package/src/api/cnpg.legacy-reads.test.ts +43 -0
- package/src/api/cnpg.pooler-runtime.test.ts +15 -0
- package/src/api/cnpg.ts +428 -10
- package/src/api/diagnose.test.ts +40 -0
- package/src/api/diagnose.ts +12 -1
- package/src/api/radarFeatures.render.test.tsx +65 -3
- package/src/api/radarFeatures.ts +6 -0
- package/src/api/reviewedActionMutation.test.tsx +164 -0
- package/src/api/reviewedActionMutation.ts +66 -0
- package/src/api/timelineSource.test.ts +9 -0
- package/src/api/timelineSource.ts +8 -0
- package/src/api/traffic.sources.test.ts +55 -0
- package/src/api/traffic.ts +173 -14
- package/src/components/applications/ApplicationsView.tsx +3 -0
- package/src/components/audit/AuditView.tsx +9 -0
- package/src/components/capacity/CapacityActivity.tsx +3 -31
- package/src/components/capacity/CapacityDemand.tsx +4 -36
- package/src/components/capacity/CapacityOverview.tsx +217 -56
- package/src/components/capacity/CapacityPoolDetail.tsx +24 -62
- package/src/components/capacity/CapacityView.test.tsx +354 -1
- package/src/components/capacity/ClusterSchedulingCard.tsx +2 -10
- package/src/components/capacity/certaintyGlyph.test.tsx +2 -9
- package/src/components/capacity/routes.ts +42 -0
- package/src/components/capacity/shared.tsx +23 -150
- package/src/components/capacity/workspaceRoute.ts +18 -0
- package/src/components/cnpg/CNPGActionHost.compatibility.test.tsx +30 -0
- package/src/components/cnpg/CNPGArchivingRepair.tsx +191 -0
- package/src/components/cnpg/CNPGBlockingSessions.test.tsx +50 -0
- package/src/components/cnpg/CNPGBlockingSessions.tsx +333 -0
- package/src/components/cnpg/CNPGClusterActivity.test.tsx +11 -0
- package/src/components/cnpg/CNPGClusterActivity.tsx +18 -9
- package/src/components/cnpg/CNPGClusterLogs.compatibility.test.tsx +130 -0
- package/src/components/cnpg/CNPGClusterLogs.empty.test.tsx +29 -0
- package/src/components/cnpg/CNPGClusterLogs.test.ts +9 -0
- package/src/components/cnpg/CNPGClusterLogs.tsx +81 -24
- package/src/components/cnpg/CNPGClusterTabs.test.tsx +152 -0
- package/src/components/cnpg/CNPGClusterTabs.tsx +187 -0
- package/src/components/cnpg/CNPGClustersKindView.tsx +46 -0
- package/src/components/cnpg/CNPGConfiguration.test.tsx +223 -0
- package/src/components/cnpg/CNPGConfiguration.tsx +190 -0
- package/src/components/cnpg/CNPGCreateClusterDialog.test.tsx +113 -0
- package/src/components/cnpg/CNPGCreateClusterDialog.tsx +74 -0
- package/src/components/cnpg/CNPGDeclarations.test.ts +107 -0
- package/src/components/cnpg/CNPGDeclarations.tsx +236 -78
- package/src/components/cnpg/CNPGDetailPage.test.tsx +62 -0
- package/src/components/cnpg/CNPGDetailPage.tsx +159 -98
- package/src/components/cnpg/CNPGDrawerTrail.tsx +2 -2
- package/src/components/cnpg/CNPGInvestigationAction.test.tsx +30 -0
- package/src/components/cnpg/CNPGOperator.test.tsx +34 -0
- package/src/components/cnpg/CNPGOperator.tsx +78 -30
- package/src/components/cnpg/CNPGOperatorBanner.tsx +39 -0
- package/src/components/cnpg/CNPGOperatorConditions.test.tsx +45 -0
- package/src/components/cnpg/CNPGOperatorConditions.tsx +16 -0
- package/src/components/cnpg/CNPGOperatorConfig.test.tsx +27 -0
- package/src/components/cnpg/CNPGOperatorDiagnosis.tsx +247 -0
- package/src/components/cnpg/CNPGOverview.test.tsx +171 -0
- package/src/components/cnpg/CNPGOverview.tsx +238 -63
- package/src/components/cnpg/CNPGParametersInEffect.test.tsx +51 -0
- package/src/components/cnpg/CNPGParametersInEffect.tsx +120 -0
- package/src/components/cnpg/CNPGPerformance.prerequisite.test.tsx +48 -0
- package/src/components/cnpg/CNPGPerformance.test.tsx +73 -0
- package/src/components/cnpg/CNPGPerformance.tsx +477 -0
- package/src/components/cnpg/CNPGPooling.test.tsx +89 -0
- package/src/components/cnpg/CNPGPooling.tsx +97 -40
- package/src/components/cnpg/CNPGProtection.test.tsx +145 -0
- package/src/components/cnpg/CNPGProtection.tsx +230 -124
- package/src/components/cnpg/CNPGReplicationTab.test.tsx +87 -0
- package/src/components/cnpg/CNPGReplicationTab.tsx +212 -0
- package/src/components/cnpg/CNPGReplicationView.tsx +264 -0
- package/src/components/cnpg/CNPGStorage.test.tsx +140 -0
- package/src/components/cnpg/CNPGStorage.tsx +619 -0
- package/src/components/cnpg/CNPGSummaryHost.overview.test.tsx +44 -0
- package/src/components/cnpg/CNPGSummaryHost.test.tsx +23 -0
- package/src/components/cnpg/CNPGSummaryHost.tsx +228 -54
- package/src/components/cnpg/CNPGTrends.denied.test.tsx +33 -0
- package/src/components/cnpg/CNPGTrends.missing.test.tsx +69 -0
- package/src/components/cnpg/CNPGTrends.test.ts +38 -0
- package/src/components/cnpg/CNPGTrends.tsx +702 -0
- package/src/components/cnpg/CNPGView.tsx +34 -21
- package/src/components/cnpg/CNPGWorkspace.compatibility.test.tsx +90 -0
- package/src/components/cnpg/actions/CNPGClusterActions.dialog.test.tsx +79 -0
- package/src/components/cnpg/actions/CNPGClusterActions.test.tsx +142 -0
- package/src/components/cnpg/actions/CNPGClusterActions.tsx +853 -0
- package/src/components/cnpg/actions/CNPGConnectButton.read.test.tsx +40 -0
- package/src/components/cnpg/actions/CNPGConnectButton.test.ts +25 -0
- package/src/components/cnpg/actions/CNPGConnectButton.tsx +131 -0
- package/src/components/cnpg/actions/CNPGDestroyInstanceDialog.tsx +185 -0
- package/src/components/cnpg/actions/CNPGInstanceActions.test.tsx +38 -0
- package/src/components/cnpg/actions/CNPGInstanceActions.tsx +74 -0
- package/src/components/cnpg/actions/CNPGMaintenanceBanner.tsx +53 -0
- package/src/components/cnpg/actions/CNPGPoolerActions.test.tsx +35 -0
- package/src/components/cnpg/actions/CNPGPoolerActions.tsx +79 -0
- package/src/components/cnpg/actions/CNPGReportDialog.test.tsx +12 -0
- package/src/components/cnpg/actions/CNPGReportDialog.tsx +91 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.run.test.tsx +58 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.test.tsx +54 -0
- package/src/components/cnpg/actions/CNPGScheduleActions.tsx +208 -0
- package/src/components/cnpg/actions/actionModel.test.ts +111 -0
- package/src/components/cnpg/actions/actionModel.ts +127 -0
- package/src/components/cnpg/actions/renderCNPGHeaderActions.tsx +60 -0
- package/src/components/cnpg/actions/useCNPGWriteGuard.tsx +58 -0
- package/src/components/cnpg/actions/useOpenCNPGPsql.ts +22 -0
- package/src/components/cnpg/archivingRepair.test.ts +104 -0
- package/src/components/cnpg/archivingRepair.ts +145 -0
- package/src/components/cnpg/baseBackup.test.ts +36 -0
- package/src/components/cnpg/baseBackup.ts +36 -0
- package/src/components/cnpg/blocking.test.ts +59 -0
- package/src/components/cnpg/blocking.ts +131 -0
- package/src/components/cnpg/coverageText.test.ts +27 -0
- package/src/components/cnpg/creation/CNPGTargetFields.tsx +110 -0
- package/src/components/cnpg/creation/targetModel.test.ts +137 -0
- package/src/components/cnpg/creation/targetModel.ts +149 -0
- package/src/components/cnpg/fleetStatus.test.ts +46 -0
- package/src/components/cnpg/fleetStatus.ts +43 -0
- package/src/components/cnpg/formFields.ts +2 -0
- package/src/components/cnpg/grantText.test.tsx +11 -0
- package/src/components/cnpg/host.test.tsx +24 -0
- package/src/components/cnpg/host.tsx +101 -0
- package/src/components/cnpg/inspectModel.test.ts +120 -0
- package/src/components/cnpg/inspectModel.ts +140 -0
- package/src/components/cnpg/logicalSlots.test.ts +30 -0
- package/src/components/cnpg/logicalSlots.ts +29 -0
- package/src/components/cnpg/lsn.test.ts +40 -0
- package/src/components/cnpg/lsn.ts +43 -0
- package/src/components/cnpg/operations/CNPGFleetOperation.tsx +30 -0
- package/src/components/cnpg/operations/CNPGOperationTracker.test.tsx +247 -0
- package/src/components/cnpg/operations/CNPGOperationTracker.tsx +255 -0
- package/src/components/cnpg/operations/model.test.ts +510 -0
- package/src/components/cnpg/operations/model.ts +499 -0
- package/src/components/cnpg/operations/presentation.test.ts +80 -0
- package/src/components/cnpg/operations/presentation.ts +84 -0
- package/src/components/cnpg/operations/store.ts +98 -0
- package/src/components/cnpg/operatorStatus.test.ts +134 -0
- package/src/components/cnpg/operatorStatus.ts +194 -0
- package/src/components/cnpg/paths.test.ts +54 -0
- package/src/components/cnpg/paths.ts +47 -4
- package/src/components/cnpg/protection/CNPGAttachArchiveDialog.tsx +185 -0
- package/src/components/cnpg/protection/CNPGCreateScheduleDialog.tsx +196 -0
- package/src/components/cnpg/protection/CNPGProtectionSetup.test.tsx +121 -0
- package/src/components/cnpg/protection/CNPGProtectionSetup.tsx +452 -0
- package/src/components/cnpg/protection/CNPGScheduleInput.tsx +115 -0
- package/src/components/cnpg/protection/CNPGScheduleRepairButton.tsx +120 -0
- package/src/components/cnpg/protection/scheduleModel.test.ts +19 -0
- package/src/components/cnpg/protection/scheduleModel.ts +16 -0
- package/src/components/cnpg/recovery/CNPGRecoveryPoint.tsx +220 -0
- package/src/components/cnpg/recovery/CNPGRestoreButton.test.tsx +29 -0
- package/src/components/cnpg/recovery/CNPGRestoreButton.tsx +26 -0
- package/src/components/cnpg/recovery/CNPGRestoreChecks.test.tsx +23 -0
- package/src/components/cnpg/recovery/CNPGRestoreChecks.tsx +153 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.back.test.tsx +149 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.test.tsx +46 -0
- package/src/components/cnpg/recovery/CNPGRestoreDialog.tsx +333 -0
- package/src/components/cnpg/recovery/CNPGRestoreProgress.tsx +182 -0
- package/src/components/cnpg/recovery/CNPGRestoreValidation.tsx +216 -0
- package/src/components/cnpg/recovery/restoreModel.test.ts +709 -0
- package/src/components/cnpg/recovery/restoreModel.ts +685 -0
- package/src/components/cnpg/recovery/restoreOperation.ts +56 -0
- package/src/components/cnpg/refresh.test.ts +38 -0
- package/src/components/cnpg/refresh.ts +17 -0
- package/src/components/cnpg/refreshNotice.test.tsx +29 -0
- package/src/components/cnpg/routes.test.ts +34 -3
- package/src/components/cnpg/routes.ts +60 -48
- package/src/components/cnpg/runtimeAssessment.ts +198 -0
- package/src/components/cnpg/runtimeModel.test.ts +175 -0
- package/src/components/cnpg/runtimeModel.ts +196 -0
- package/src/components/cnpg/runtimeParts.test.tsx +17 -0
- package/src/components/cnpg/runtimeParts.tsx +79 -0
- package/src/components/cnpg/shared.test.tsx +59 -0
- package/src/components/cnpg/shared.tsx +100 -182
- package/src/components/cnpg/storageModel.test.ts +107 -0
- package/src/components/cnpg/storageModel.ts +101 -0
- package/src/components/cnpg/trendSamples.test.ts +148 -0
- package/src/components/cnpg/trendSamples.ts +222 -0
- package/src/components/cnpg/useCNPGClusterAssessment.ts +30 -0
- package/src/components/cnpg/useCNPGKubectlContext.test.tsx +45 -0
- package/src/components/cnpg/useCNPGKubectlContext.ts +13 -0
- package/src/components/cnpg/useCNPGNavigate.test.tsx +58 -0
- package/src/components/cnpg/useCNPGNavigate.ts +21 -0
- package/src/components/cnpg/useCNPGPoolerLive.ts +46 -0
- package/src/components/cnpg/useCNPGScreenParams.ts +24 -0
- package/src/components/cnpg/useCNPGSidebarWorkspace.ts +66 -16
- package/src/components/cnpg/workspaceRoute.ts +23 -0
- package/src/components/cost/ApplicationCostTab.test.ts +7 -0
- package/src/components/cost/ApplicationCostTab.tsx +7 -7
- package/src/components/cost/CostView.test.ts +2 -1
- package/src/components/cost/CostView.tsx +0 -2
- package/src/components/cost/WorkloadCostTab.tsx +2 -4
- package/src/components/cost/errors.ts +3 -1
- package/src/components/cost/source.test.ts +10 -0
- package/src/components/cost/source.ts +5 -1
- package/src/components/diagnose/AgentSetupNotice.test.tsx +58 -0
- package/src/components/diagnose/AgentSetupNotice.tsx +142 -51
- package/src/components/diagnose/ApplyDialog.ack.test.tsx +47 -0
- package/src/components/diagnose/ApplyDialog.test.tsx +59 -6
- package/src/components/diagnose/ApplyDialog.tsx +26 -28
- package/src/components/diagnose/DiagnoseContext.test.ts +43 -0
- package/src/components/diagnose/DiagnoseContext.tsx +136 -54
- package/src/components/diagnose/DiagnoseSurface.tsx +16 -3
- package/src/components/diagnose/InvestigationView.tsx +39 -2
- package/src/components/diagnose/LocalDiagnoseAction.tsx +40 -20
- package/src/components/diagnose/agentCatalog.ts +2 -2
- package/src/components/diagnose/diagnoseEvidenceTypes.ts +2 -0
- package/src/components/diagnose/investigationEvidence/adapters/issues.ts +8 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +16 -0
- package/src/components/dock/BottomDock.tsx +2 -0
- package/src/components/dock/TerminalTab.test.tsx +17 -0
- package/src/components/dock/TerminalTab.tsx +7 -3
- package/src/components/dock/TrafficFlowListTab.tsx +27 -6
- package/src/components/execution/BatchExecutionView.tsx +11 -11
- package/src/components/gitops/ArgoResourceDiffLoader.tsx +4 -2
- package/src/components/gitops/GitOpsView.tsx +16 -23
- package/src/components/helm/ChartBrowser.tsx +3 -3
- package/src/components/helm/HelmCompareRoute.test.tsx +59 -0
- package/src/components/helm/HelmCompareRoute.tsx +57 -26
- package/src/components/helm/HelmReleaseDrawer.test.ts +55 -3
- package/src/components/helm/HelmReleaseDrawer.tsx +50 -12
- package/src/components/helm/RevisionHistory.tsx +3 -2
- package/src/components/helm/TrackChartSourceDialog.render.test.tsx +120 -0
- package/src/components/helm/TrackChartSourceDialog.test.ts +50 -0
- package/src/components/helm/TrackChartSourceDialog.tsx +153 -176
- package/src/components/helm/ValuesViewer.test.tsx +301 -0
- package/src/components/helm/ValuesViewer.tsx +169 -78
- package/src/components/home/ActivitySummary.tsx +2 -1
- package/src/components/home/ClusterHealthCard.tsx +7 -0
- package/src/components/home/HomeView.tsx +20 -9
- package/src/components/home/ProblemsPanel.test.tsx +81 -0
- package/src/components/home/TopologyPreview.tsx +2 -2
- package/src/components/home/mcpToolCatalog.ts +1 -1
- package/src/components/issues/IssuesPane.render.test.tsx +122 -0
- package/src/components/issues/IssuesPane.test.ts +65 -1
- package/src/components/issues/IssuesPane.tsx +188 -25
- package/src/components/issues/uncheckedSources.ts +21 -0
- package/src/components/resource/PVCUsageBar.render.test.tsx +32 -0
- package/src/components/resource/PVCUsageBar.test.tsx +24 -0
- package/src/components/resource/PVCUsageBar.tsx +15 -6
- package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +3 -1
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
- package/src/components/resources/CompositeRenderer.tsx +4 -4
- package/src/components/resources/ResourceDetailDrawer.tsx +2 -2
- package/src/components/resources/ResourcesView.tsx +26 -12
- package/src/components/resources/renderers/KyvernoPolicyQueued.tsx +2 -1
- package/src/components/resources/renderers/NodeRenderer.tsx +7 -1
- package/src/components/resources/renderers/ServiceRenderer.test.tsx +81 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +19 -14
- package/src/components/settings/LocalIntegrationStatus.test.tsx +52 -0
- package/src/components/settings/LocalIntegrationStatus.tsx +3 -4
- package/src/components/settings/SettingsDialog.tsx +150 -34
- package/src/components/shared/LargeClusterNamespacePicker.tsx +11 -5
- package/src/components/timeline/TimelineList.tsx +25 -24
- package/src/components/timeline/TimelineLoadError.test.tsx +62 -0
- package/src/components/timeline/TimelineLoadError.tsx +96 -0
- package/src/components/timeline/TimelineRetentionNote.test.tsx +49 -0
- package/src/components/timeline/TimelineRetentionNote.tsx +20 -0
- package/src/components/timeline/TimelineView.tsx +25 -45
- package/src/components/timeline/ViewModeToggle.tsx +20 -0
- package/src/components/timeline/useStickyError.test.tsx +47 -0
- package/src/components/timeline/useStickyError.ts +15 -0
- package/src/components/traffic/TrafficConnectionsTable.tsx +144 -0
- package/src/components/traffic/TrafficFilterSidebar.tsx +22 -1
- package/src/components/traffic/TrafficFlowListContext.tsx +12 -4
- package/src/components/traffic/TrafficFocusSearch.tsx +172 -0
- package/src/components/traffic/TrafficGraph.tsx +109 -26
- package/src/components/traffic/TrafficGraphTooLarge.tsx +150 -0
- package/src/components/traffic/TrafficView.tsx +582 -149
- package/src/components/traffic/trafficFilters.test.ts +398 -1
- package/src/components/traffic/trafficFilters.ts +408 -0
- package/src/components/ui/DiagnosticsOverlay.tsx +34 -2
- package/src/components/ui/Markdown.test.tsx +65 -0
- package/src/components/ui/Markdown.tsx +144 -127
- package/src/components/whats-new/releaseNotes.ts +63 -1
- package/src/components/workload/WorkloadView.test.ts +11 -1
- package/src/components/workload/WorkloadView.tsx +162 -529
- package/src/components/workspace/layout.test.tsx +29 -0
- package/src/components/workspace/layout.tsx +259 -0
- package/src/components/workspace/table.ts +7 -0
- package/src/context/TimelineCustomization.tsx +15 -0
- package/src/contexts/CapabilitiesContext.tsx +1 -1
- package/src/hooks/useGitOpsWriteGuard.test.tsx +457 -0
- package/src/hooks/useGitOpsWriteGuard.ts +193 -0
- package/src/hooks/useResolvedGitOpsOwner.ts +256 -0
- package/src/integrations/resourceHost.test.tsx +55 -0
- package/src/integrations/resourceHost.ts +99 -0
- package/src/integrations/resourceHosts.test.tsx +141 -0
- package/src/integrations/resourceHosts.tsx +119 -0
- package/src/integrations/workspaceRoute.ts +20 -0
- package/src/integrations/workspaceRoutes.test.ts +40 -0
- package/src/integrations/workspaceRoutes.ts +22 -0
- package/src/integrations/workspaceScreens.tsx +14 -0
- package/src/utils/drawer-trail.ts +35 -0
- package/src/utils/kindFilterStore.test.ts +147 -0
- package/src/utils/kindFilterStore.ts +91 -0
- package/src/utils/navigation.test.ts +13 -1
- package/src/utils/navigation.ts +39 -2
- package/src/utils/page-links.ts +21 -0
- package/src/utils/tabStorage.ts +40 -0
- package/src/utils/viewMemory.test.ts +78 -0
- package/src/utils/viewMemory.ts +54 -0
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { useCallback } from 'react'
|
|
2
|
-
import { useSearchParams } from 'react-router-dom'
|
|
3
|
-
import { WorkloadLogsViewer, type WorkloadLogsFetchParams, type WorkloadLogsResult } from '@skyhook-io/k8s-ui'
|
|
4
|
-
import { fetchJSON } from '../../api/client'
|
|
2
|
+
import { useLocation, useSearchParams } from 'react-router-dom'
|
|
3
|
+
import { PaneLoader, WorkloadLogsViewer, type WorkloadLogsFetchParams, type WorkloadLogsResult } from '@skyhook-io/k8s-ui'
|
|
4
|
+
import { fetchJSON, useCapabilities, useRadarFeature } from '../../api/client'
|
|
5
5
|
import { getApiBase, getCredentialsMode } from '../../api/config'
|
|
6
6
|
import { useDesktopDownload } from '../../hooks/useDesktopDownload'
|
|
7
7
|
import { useTheme } from '../../context/ThemeContext'
|
|
8
|
+
import { CNPGIntervalBanner, useCNPGIntervalParams } from './CNPGTrends'
|
|
9
|
+
import { useCNPGFleet } from './useCNPGSidebarWorkspace'
|
|
10
|
+
import { useCNPGNavigate } from './useCNPGNavigate'
|
|
11
|
+
import { cnpgClusterProblemsPath, cnpgWithinDetail } from './paths'
|
|
8
12
|
|
|
9
13
|
function logsPath(namespace: string, name: string) {
|
|
10
14
|
return `/cnpg/clusters/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/logs`
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
function query(params: WorkloadLogsFetchParams, tailDefault?: number) {
|
|
17
|
+
export function query(params: WorkloadLogsFetchParams, tailDefault?: number, allContainersSupported = true) {
|
|
14
18
|
const q = new URLSearchParams()
|
|
15
|
-
|
|
19
|
+
const container = params.container || (allContainersSupported ? 'all' : '')
|
|
20
|
+
if (container && (container !== 'all' || allContainersSupported)) q.set('container', container)
|
|
16
21
|
const tail = params.tailLines ?? tailDefault
|
|
17
22
|
if (tail) q.set('tailLines', String(tail))
|
|
18
23
|
if (params.sinceSeconds) q.set('sinceSeconds', String(params.sinceSeconds))
|
|
@@ -20,43 +25,95 @@ function query(params: WorkloadLogsFetchParams, tailDefault?: number) {
|
|
|
20
25
|
return s ? `?${s}` : ''
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
// An interval is read from its start with both bounds applied server-side; the
|
|
29
|
+
// viewer's line-count and since selectors do not apply to it.
|
|
30
|
+
export function intervalQuery(params: WorkloadLogsFetchParams, since: string, until: string) {
|
|
31
|
+
const q = new URLSearchParams({ sinceTime: since, untilTime: until })
|
|
32
|
+
q.set('container', params.container || 'all')
|
|
33
|
+
return `?${q.toString()}`
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
/**
|
|
24
37
|
* Logs merged from every instance Pod of a CloudNativePG Cluster. `?pod=`
|
|
25
|
-
* preselects one instance (the fleet's "Logs" action and "Open instance logs")
|
|
38
|
+
* preselects one instance (the fleet's "Logs" action and "Open instance logs"),
|
|
39
|
+
* `?container=` one container (e.g. the plugin-barman-cloud sidecar);
|
|
40
|
+
* `?since=&until=` bounds them to an interval selected on a History chart.
|
|
26
41
|
*/
|
|
27
42
|
export function CNPGClusterLogs({ namespace, name }: { namespace: string; name: string }) {
|
|
28
43
|
const [searchParams] = useSearchParams()
|
|
44
|
+
const navigate = useCNPGNavigate()
|
|
45
|
+
const location = useLocation()
|
|
46
|
+
const { fleet } = useCNPGFleet([namespace])
|
|
47
|
+
const row = fleet?.rows.find((r) => r.namespace === namespace && r.name === name)
|
|
48
|
+
const problem = row?.problems.find((p) => p.job === 'initdb')
|
|
49
|
+
const overviewPath = `${cnpgClusterProblemsPath(namespace, name, searchParams.get('ctx') ?? undefined)}&tab=overview`
|
|
50
|
+
const inPlace = cnpgWithinDetail(location.pathname, location.search, overviewPath)
|
|
29
51
|
const pod = searchParams.get('pod')
|
|
52
|
+
const container = searchParams.get('container')
|
|
53
|
+
const interval = useCNPGIntervalParams()
|
|
30
54
|
const desktopDownload = useDesktopDownload()
|
|
31
55
|
const { theme } = useTheme()
|
|
56
|
+
const since = interval?.since
|
|
57
|
+
const until = interval?.until
|
|
32
58
|
|
|
59
|
+
const { guard, support } = useRadarFeature('cnpgWorkspace')
|
|
60
|
+
const capabilities = useCapabilities()
|
|
61
|
+
const allContainersSupported = support === 'supported'
|
|
33
62
|
const fetchAll = useCallback(
|
|
34
63
|
(params: WorkloadLogsFetchParams) =>
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
since && until
|
|
65
|
+
? guard(() => fetchJSON<WorkloadLogsResult>(`${logsPath(namespace, name)}${intervalQuery(params, since, until)}`, { signal: params.signal }))
|
|
66
|
+
: fetchJSON<WorkloadLogsResult>(`${logsPath(namespace, name)}${query(params, undefined, allContainersSupported)}`, { signal: params.signal }),
|
|
67
|
+
[guard, namespace, name, since, until, allContainersSupported],
|
|
37
68
|
)
|
|
38
|
-
const
|
|
69
|
+
const stream = useCallback(
|
|
39
70
|
(params: WorkloadLogsFetchParams) =>
|
|
40
|
-
new EventSource(`${getApiBase()}${logsPath(namespace, name)}/stream${query(params, 50)}`, {
|
|
71
|
+
new EventSource(`${getApiBase()}${logsPath(namespace, name)}/stream${query(params, 50, allContainersSupported)}`, {
|
|
41
72
|
withCredentials: getCredentialsMode() === 'include',
|
|
42
73
|
}),
|
|
43
|
-
[namespace, name],
|
|
74
|
+
[namespace, name, allContainersSupported],
|
|
44
75
|
)
|
|
45
76
|
|
|
77
|
+
if (interval && support === 'unknown') return capabilities.isPending && capabilities.fetchStatus !== 'paused'
|
|
78
|
+
? <PaneLoader label="Checking interval support…" className="flex-1" />
|
|
79
|
+
: <div className="space-y-2 p-3">
|
|
80
|
+
<CNPGIntervalBanner since={interval.since} until={interval.until} note="Interval log support could not be checked. Clear this interval to read ordinary logs." onClear={interval.clear} />
|
|
81
|
+
<button type="button" className="btn-secondary" disabled={capabilities.isFetching || capabilities.fetchStatus === 'paused'} onClick={() => void capabilities.refetch()}>{capabilities.fetchStatus === 'paused' ? 'Waiting for connection' : capabilities.isFetching ? 'Checking…' : 'Retry'}</button>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
46
84
|
return (
|
|
47
|
-
<div className="h-full">
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
85
|
+
<div className="flex h-full flex-col">
|
|
86
|
+
{interval && (
|
|
87
|
+
<div className="px-3 pt-3">
|
|
88
|
+
<CNPGIntervalBanner
|
|
89
|
+
since={interval.since}
|
|
90
|
+
until={interval.until}
|
|
91
|
+
note={support === 'unsupported' ? 'This interval needs a newer Radar. Clear it to read ordinary logs.' : 'Selected on a History chart. Lines from the start of the interval, up to 64 KiB per instance; the line-count selector does not apply and streaming is off.'}
|
|
92
|
+
onClear={interval.clear}
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
<div className="min-h-0 flex-1">
|
|
97
|
+
<WorkloadLogsViewer
|
|
98
|
+
key={`${pod ?? ''}|${container ?? ''}|${since ?? ''}|${until ?? ''}|${allContainersSupported}`}
|
|
99
|
+
name={name}
|
|
100
|
+
allowAllContainers={allContainersSupported}
|
|
101
|
+
disableSourceControlsWithoutSource
|
|
102
|
+
emptySourceState={<div className="space-y-2 text-sm text-theme-text-secondary">
|
|
103
|
+
<p>No instance log source yet. {row?.hibernated ? 'The Cluster is hibernated.' : problem?.title}</p>
|
|
104
|
+
<button type="button" className="text-accent-text hover:underline" onClick={() => navigate(inPlace ?? overviewPath, { replace: !!inPlace, state: location.state })}>{problem ? 'See Overview’s problem →' : 'Open Overview →'}</button>
|
|
105
|
+
</div>}
|
|
106
|
+
fetchAll={fetchAll}
|
|
107
|
+
createStream={interval ? undefined : stream}
|
|
108
|
+
overrideDownload={desktopDownload}
|
|
109
|
+
forceDark={theme === 'dark' ? true : undefined}
|
|
110
|
+
// Light logs on a dark app are never wanted: dark theme pins the palette,
|
|
111
|
+
// light theme only sets where it starts and leaves the toggle available.
|
|
112
|
+
defaultDark={false}
|
|
113
|
+
initialPods={pod ? [pod] : undefined}
|
|
114
|
+
initialContainer={container && container !== 'all' ? container : allContainersSupported ? undefined : 'postgres'}
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
60
117
|
</div>
|
|
61
118
|
)
|
|
62
119
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom'
|
|
3
|
+
import { beforeEach, expect, it, vi } from 'vitest'
|
|
4
|
+
import type { CNPGDimension } from '@skyhook-io/k8s-ui'
|
|
5
|
+
import { CNPGBackupsTab, CNPGStorageTab } from './CNPGClusterTabs'
|
|
6
|
+
const state = vi.hoisted(() => ({
|
|
7
|
+
coverage: 'full',
|
|
8
|
+
primary: {} as any,
|
|
9
|
+
storageProps: {} as any,
|
|
10
|
+
schedules: [] as any[],
|
|
11
|
+
dimensions: [] as CNPGDimension[],
|
|
12
|
+
}))
|
|
13
|
+
const cluster = {
|
|
14
|
+
apiVersion: 'postgresql.cnpg.io/v1',
|
|
15
|
+
kind: 'Cluster',
|
|
16
|
+
metadata: { name: 'pg', namespace: 'db' },
|
|
17
|
+
status: { currentPrimary: 'pg-1' } as any,
|
|
18
|
+
spec: {} as any,
|
|
19
|
+
}
|
|
20
|
+
vi.mock('../../api/client', () => ({ useRadarFeature: () => ({ support: 'supported' }) }))
|
|
21
|
+
vi.mock('../../api/cnpg', () => ({
|
|
22
|
+
useCNPGRuntime: () => ({ data: { permission: { proxy: 'denied' }, instances: [state.primary] } }),
|
|
23
|
+
}))
|
|
24
|
+
vi.mock('./useCNPGClusterAssessment', () => ({
|
|
25
|
+
useCNPGClusterAssessment: () => ({
|
|
26
|
+
dimensions: state.dimensions,
|
|
27
|
+
row: {
|
|
28
|
+
cluster,
|
|
29
|
+
problems: [],
|
|
30
|
+
protection: { walArchiving: { text: 'Not archived: no destination configured', tone: 'neutral' } },
|
|
31
|
+
},
|
|
32
|
+
query: {
|
|
33
|
+
data: { coverage: { backups: { state: state.coverage } }, objects: { backups: [], clusters: [cluster] } },
|
|
34
|
+
},
|
|
35
|
+
runtime: {},
|
|
36
|
+
}),
|
|
37
|
+
}))
|
|
38
|
+
vi.mock('./useCNPGSidebarWorkspace', () => ({
|
|
39
|
+
useCNPGFleet: () => ({
|
|
40
|
+
query: {
|
|
41
|
+
data: {
|
|
42
|
+
installed: true,
|
|
43
|
+
coverage: { backups: { state: state.coverage } },
|
|
44
|
+
objects: { backups: [], scheduledBackups: state.schedules },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
fleet: { rows: [{ name: 'pg', namespace: 'db', cluster }] },
|
|
48
|
+
}),
|
|
49
|
+
}))
|
|
50
|
+
vi.mock('../../api/cnpg-recovery', () => ({ useCNPGRestoreCapability: () => ({ data: { allowed: true } }) }))
|
|
51
|
+
vi.mock('./CNPGProtection', () => ({ CNPGProtection: () => null }))
|
|
52
|
+
vi.mock('./CNPGArchivingRepair', () => ({ CNPGArchivingRepair: () => null }))
|
|
53
|
+
vi.mock('./CNPGStorage', () => ({
|
|
54
|
+
CNPGStorage: (props: any) => {
|
|
55
|
+
state.storageProps = props
|
|
56
|
+
return null
|
|
57
|
+
},
|
|
58
|
+
}))
|
|
59
|
+
vi.mock('./recovery/CNPGRestoreButton', () => ({
|
|
60
|
+
CNPGRestoreButton: ({ disabledReason }: { disabledReason?: string }) => (
|
|
61
|
+
<button disabled={!!disabledReason}>Restore</button>
|
|
62
|
+
),
|
|
63
|
+
}))
|
|
64
|
+
vi.mock('../../context/ConnectionContext', () => ({ useConnection: () => ({ connection: { context: 'test' } }) }))
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
state.coverage = 'full'
|
|
67
|
+
state.primary = {}
|
|
68
|
+
state.schedules = []
|
|
69
|
+
state.dimensions = []
|
|
70
|
+
cluster.spec = {}
|
|
71
|
+
delete cluster.status.lastSuccessfulBackup
|
|
72
|
+
})
|
|
73
|
+
it('passes primary identity through to Storage when database reads are denied', () => {
|
|
74
|
+
state.primary = { pod: 'pg-1', role: 'primary', metrics: { state: 'denied' } }
|
|
75
|
+
renderToStaticMarkup(<CNPGStorageTab namespace="db" name="pg" />)
|
|
76
|
+
expect(state.storageProps.primary).toBe(state.primary)
|
|
77
|
+
expect(state.storageProps.runtime.data.permission.proxy).toBe('denied')
|
|
78
|
+
})
|
|
79
|
+
it('uses the shared restore assessment for the Backups button', () => {
|
|
80
|
+
const render = () =>
|
|
81
|
+
renderToStaticMarkup(
|
|
82
|
+
<MemoryRouter>
|
|
83
|
+
<CNPGBackupsTab namespace="db" name="pg" onInspect={() => {}} />
|
|
84
|
+
</MemoryRouter>,
|
|
85
|
+
)
|
|
86
|
+
state.coverage = 'full'
|
|
87
|
+
expect(render()).toContain('disabled=""')
|
|
88
|
+
expect(render()).toContain('Nothing to restore from yet')
|
|
89
|
+
state.coverage = 'denied'
|
|
90
|
+
expect(render()).not.toContain('disabled=""')
|
|
91
|
+
expect(render()).not.toContain('Nothing to restore from yet')
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('starts a single guided setup path beside restore, without treating a schedule as protection', () => {
|
|
95
|
+
state.schedules = [{ metadata: { name: 'pg-nightly', namespace: 'db' }, spec: { cluster: { name: 'pg' } } }]
|
|
96
|
+
const html = renderToStaticMarkup(
|
|
97
|
+
<MemoryRouter>
|
|
98
|
+
<CNPGBackupsTab namespace="db" name="pg" onInspect={() => {}} onOpenYaml={() => {}} />
|
|
99
|
+
</MemoryRouter>,
|
|
100
|
+
)
|
|
101
|
+
expect(html).toContain('Set up backups…')
|
|
102
|
+
expect(html).toContain('Nothing to restore from yet')
|
|
103
|
+
expect(html).not.toContain('Configure spec.backup.barmanObjectStore')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('keeps the protection verdict ahead of setup without repeating the blocker', () => {
|
|
107
|
+
state.schedules = [{ metadata: { name: 'pg-nightly', namespace: 'db' }, spec: { cluster: { name: 'pg' } } }]
|
|
108
|
+
state.dimensions = [
|
|
109
|
+
{
|
|
110
|
+
id: 'protection',
|
|
111
|
+
label: 'Backups',
|
|
112
|
+
tone: 'degraded',
|
|
113
|
+
text: 'Backup schedule pg-nightly cannot run: no backup destination',
|
|
114
|
+
source: 'ScheduledBackup method against its target Cluster spec',
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
const html = renderToStaticMarkup(
|
|
118
|
+
<MemoryRouter>
|
|
119
|
+
<CNPGBackupsTab namespace="db" name="pg" onInspect={() => {}} onOpenYaml={() => {}} />
|
|
120
|
+
</MemoryRouter>,
|
|
121
|
+
)
|
|
122
|
+
expect(html.match(/cannot run:/g)).toHaveLength(1)
|
|
123
|
+
expect(html.indexOf(state.dimensions[0].text)).toBeLessThan(html.indexOf('Set up backups…'))
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('does not invent a missing destination for an enabled third-party plugin', () => {
|
|
127
|
+
cluster.spec.plugins = [{ name: 'third-party-backup' }]
|
|
128
|
+
const html = renderToStaticMarkup(
|
|
129
|
+
<MemoryRouter>
|
|
130
|
+
<CNPGBackupsTab namespace="db" name="pg" onInspect={() => {}} />
|
|
131
|
+
</MemoryRouter>,
|
|
132
|
+
)
|
|
133
|
+
expect(html).not.toContain('Configure spec.backup')
|
|
134
|
+
delete cluster.spec.plugins
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('passes recovery certainty instead of equating a destination with a usable backup', () => {
|
|
138
|
+
const renderStorage = () => renderToStaticMarkup(<CNPGStorageTab namespace="db" name="pg" />)
|
|
139
|
+
state.coverage = 'full'
|
|
140
|
+
cluster.spec.backup = { barmanObjectStore: { destinationPath: 's3://backups' } }
|
|
141
|
+
renderStorage()
|
|
142
|
+
expect(state.storageProps.restoreState).toBe('none')
|
|
143
|
+
cluster.status.lastSuccessfulBackup = '2026-10-01T00:00:00Z'
|
|
144
|
+
renderStorage()
|
|
145
|
+
expect(state.storageProps.restoreState).toBe('available')
|
|
146
|
+
delete cluster.status.lastSuccessfulBackup
|
|
147
|
+
state.coverage = 'denied'
|
|
148
|
+
renderStorage()
|
|
149
|
+
expect(state.storageProps.restoreState).toBe('unknown')
|
|
150
|
+
state.coverage = 'full'
|
|
151
|
+
delete cluster.spec.backup
|
|
152
|
+
})
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useRef, type ReactNode } from 'react'
|
|
2
|
+
import type { RenderDiagnoseAction } from '../../context/DiagnoseCustomization'
|
|
3
|
+
import { useSearchParams } from 'react-router-dom'
|
|
4
|
+
import { CNPGDimensionMark, CNPGDimensionVerdict, CNPGServingStatus, coverageReadable, toneTextClass, type CNPGDimension, type CNPGFleetRow } from '@skyhook-io/k8s-ui'
|
|
5
|
+
import type { SelectedResource } from '../../types'
|
|
6
|
+
import { useCNPGRuntime } from '../../api/cnpg'
|
|
7
|
+
import { CNPGStorage } from './CNPGStorage'
|
|
8
|
+
import { CNPGArchivingRepair } from './CNPGArchivingRepair'
|
|
9
|
+
import { CNPGProtection } from './CNPGProtection'
|
|
10
|
+
import { CNPGRestoreValidation } from './recovery/CNPGRestoreValidation'
|
|
11
|
+
import { CNPGRestoreButton } from './recovery/CNPGRestoreButton'
|
|
12
|
+
import { assessRestoreSources } from './recovery/restoreModel'
|
|
13
|
+
import { useCNPGRestoreCapability } from '../../api/cnpg-recovery'
|
|
14
|
+
import { CNPGScreenGate } from './shared'
|
|
15
|
+
import { useCNPGClusterAssessment } from './useCNPGClusterAssessment'
|
|
16
|
+
import { useCNPGFleet } from './useCNPGSidebarWorkspace'
|
|
17
|
+
import { CNPGProtectionSetup } from './protection/CNPGProtectionSetup'
|
|
18
|
+
|
|
19
|
+
/** The health mark beside a tab: the same assessment the Overview explains, for the dimension that tab holds. */
|
|
20
|
+
export function CNPGTabMark({ namespace, name, id }: { namespace: string; name: string; id: CNPGDimension['id'] }) {
|
|
21
|
+
const { dimensions } = useCNPGClusterAssessment(namespace, name)
|
|
22
|
+
const dimension = dimensions?.find((d) => d.id === id)
|
|
23
|
+
return dimension ? <CNPGDimensionMark dimension={dimension} /> : null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Why a tab carries its mark, as the tab's first line; nothing when the dimension is fine. */
|
|
27
|
+
export function CNPGTabVerdict({ namespace, name, id, className, alwaysShow }: { namespace: string; name: string; id: CNPGDimension['id']; className?: string; alwaysShow?: boolean }) {
|
|
28
|
+
const { dimensions } = useCNPGClusterAssessment(namespace, name)
|
|
29
|
+
const dimension = dimensions?.find((d) => d.id === id)
|
|
30
|
+
return dimension ? <CNPGDimensionVerdict dimension={dimension} className={className} alwaysShow={alwaysShow} /> : null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Whether the cluster serves writes, on its title line, opening the tab that explains it. */
|
|
34
|
+
export function CNPGClusterServing({ namespace, name, onSelect }: { namespace: string; name: string; onSelect: () => void }) {
|
|
35
|
+
const { dimensions } = useCNPGClusterAssessment(namespace, name)
|
|
36
|
+
const dimension = dimensions?.find((d) => d.id === 'serving')
|
|
37
|
+
return dimension ? <CNPGServingStatus dimension={dimension} onSelect={onSelect} /> : null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Storage: volumes, what holds WAL, and resize, with the history of both one click away. */
|
|
41
|
+
export function CNPGStorageTab({
|
|
42
|
+
namespace,
|
|
43
|
+
name,
|
|
44
|
+
onOpenHistory,
|
|
45
|
+
onOpenReplication,
|
|
46
|
+
}: {
|
|
47
|
+
namespace: string
|
|
48
|
+
name: string
|
|
49
|
+
onOpenHistory?: () => void
|
|
50
|
+
onOpenReplication?: () => void
|
|
51
|
+
}) {
|
|
52
|
+
const runtime = useCNPGRuntime(namespace, name)
|
|
53
|
+
const { row, query } = useCNPGClusterAssessment(namespace, name)
|
|
54
|
+
const clusterObject = row?.cluster
|
|
55
|
+
const primary = runtime.data?.instances.find((i) => i.role === 'primary')
|
|
56
|
+
return (
|
|
57
|
+
<div className="space-y-4 p-4">
|
|
58
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
59
|
+
<h2 className="text-sm font-semibold text-theme-text-primary">Volumes and WAL</h2>
|
|
60
|
+
{onOpenHistory && (
|
|
61
|
+
<button type="button" onClick={onOpenHistory} className="ml-auto text-xs text-accent-text hover:underline">
|
|
62
|
+
Storage history →
|
|
63
|
+
</button>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
<CNPGTabVerdict namespace={namespace} name={name} id="storage" />
|
|
67
|
+
{row && <SlotRelief row={row} onOpenReplication={onOpenReplication} />}
|
|
68
|
+
<CNPGStorage namespace={namespace} name={name} primary={primary} runtime={runtime} clusterObject={clusterObject} restoreState={assessRestoreSources(query.data, namespace, clusterObject).recoveryState} walArchiving={row?.protection.walArchiving} />
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* What to do about WAL an inactive slot holds: the standby it is kept for,
|
|
75
|
+
* whether that standby receives anything, and the two ways the WAL is freed.
|
|
76
|
+
*/
|
|
77
|
+
function SlotRelief({ row, onOpenReplication }: { row: CNPGFleetRow; onOpenReplication?: () => void }) {
|
|
78
|
+
const slots = row.problems.filter((p) => p.reason === 'CNPGInactiveSlot')
|
|
79
|
+
if (slots.length === 0) return null
|
|
80
|
+
const gaps = new Set(row.problems.filter((p) => p.reason === 'CNPGStandbyNotReceiving').map((p) => p.subject.name))
|
|
81
|
+
return (
|
|
82
|
+
<section className="rounded-xl border border-theme-border bg-theme-surface px-4 py-3 shadow-theme-sm">
|
|
83
|
+
{slots.map((p) => {
|
|
84
|
+
const standby = p.instance
|
|
85
|
+
return (
|
|
86
|
+
<div key={p.id} className="space-y-1 text-sm">
|
|
87
|
+
<div className={toneTextClass('degraded')}>{p.title}</div>
|
|
88
|
+
<p className="text-theme-text-secondary">
|
|
89
|
+
The primary keeps that WAL until {standby ?? 'the slot’s consumer'} catches up or the slot is dropped.
|
|
90
|
+
{standby && gaps.has(standby) ? ` ${standby} is not streaming from the primary, and the slot advances only while it does — even if ${standby} replays WAL from the archive meanwhile.` : ''}
|
|
91
|
+
</p>
|
|
92
|
+
{standby && (
|
|
93
|
+
<p className="text-theme-text-secondary">
|
|
94
|
+
To free it, get {standby} streaming again — restart it, after checking its logs for why it stopped — or destroy it so CloudNativePG recreates it from
|
|
95
|
+
a fresh copy of the primary; the operator drops the HA slot of an instance that no longer exists.
|
|
96
|
+
</p>
|
|
97
|
+
)}
|
|
98
|
+
{onOpenReplication && (
|
|
99
|
+
<button type="button" onClick={onOpenReplication} className="text-xs text-accent-text hover:underline">
|
|
100
|
+
{standby ? `${standby} on Replication →` : 'Replication →'}
|
|
101
|
+
</button>
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
})}
|
|
106
|
+
</section>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Backups: this cluster's recovery evidence, runs, schedules and destination, plus restore validation once it was restored. */
|
|
111
|
+
export function CNPGBackupsTab({
|
|
112
|
+
namespace,
|
|
113
|
+
name,
|
|
114
|
+
onInspect,
|
|
115
|
+
onOpenLogs,
|
|
116
|
+
onOpenOperator,
|
|
117
|
+
onOpenYaml,
|
|
118
|
+
}: {
|
|
119
|
+
namespace: string
|
|
120
|
+
name: string
|
|
121
|
+
onInspect: (r: SelectedResource) => void
|
|
122
|
+
onOpenLogs?: (pod: string, container: string) => void
|
|
123
|
+
onOpenOperator?: () => void
|
|
124
|
+
onOpenYaml?: () => void
|
|
125
|
+
}) {
|
|
126
|
+
const { query, fleet } = useCNPGFleet([namespace])
|
|
127
|
+
const { row, runtime } = useCNPGClusterAssessment(namespace, name)
|
|
128
|
+
const primary = runtime.data?.instances.find((i) => i.role === 'primary')
|
|
129
|
+
const [searchParams] = useSearchParams()
|
|
130
|
+
const archivingRepairRef = useRef<HTMLDivElement>(null)
|
|
131
|
+
const restore = useCNPGRestoreCapability(namespace)
|
|
132
|
+
const restoreBlocked = restore.data ? (restore.data.allowed ? undefined : restore.data.reason ?? 'Not allowed') : restore.isLoading ? 'Checking whether you can create a Cluster here…' : undefined
|
|
133
|
+
return (
|
|
134
|
+
<CNPGScreenGate query={query} fleet={fleet}>
|
|
135
|
+
{(data, readyFleet) => {
|
|
136
|
+
const cluster = readyFleet.rows.find((r) => r.namespace === namespace && r.name === name)?.cluster
|
|
137
|
+
const nothing = assessRestoreSources(data, namespace, cluster).disabledReason
|
|
138
|
+
return (
|
|
139
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
140
|
+
<CNPGTabVerdict namespace={namespace} name={name} id="protection" className="px-4 pt-4" />
|
|
141
|
+
<div className="flex flex-wrap items-center gap-2 px-4 pt-3">
|
|
142
|
+
<CNPGProtectionSetup key={`${namespace}/${name}`} namespace={namespace} name={name} onInspect={onInspect} onOpenYaml={onOpenYaml} onOpenOperator={onOpenOperator} onOpenArchivingRepair={() => archivingRepairRef.current?.scrollIntoView({ block: 'start' })} />
|
|
143
|
+
<CNPGRestoreButton namespace={namespace} entry={{ kind: 'cluster', name }} disabledReason={restoreBlocked ?? nothing} />
|
|
144
|
+
<span className="text-xs text-theme-text-tertiary">{restoreBlocked ?? nothing ?? 'Restores into a new Cluster beside this one; this cluster is not changed.'}</span>
|
|
145
|
+
</div>
|
|
146
|
+
{row && (
|
|
147
|
+
<div ref={archivingRepairRef} className="px-4 pt-4">
|
|
148
|
+
<CNPGArchivingRepair
|
|
149
|
+
row={row}
|
|
150
|
+
primary={primary}
|
|
151
|
+
objectStores={data.objects.objectStores ?? []}
|
|
152
|
+
backups={coverageReadable(data.coverage?.backups ?? { state: 'notInstalled' }, namespace) ? (data.objects.backups ?? []) : null}
|
|
153
|
+
onOpenLogs={onOpenLogs}
|
|
154
|
+
onOpenOperator={onOpenOperator}
|
|
155
|
+
onInspect={onInspect}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
{readyFleet.rows.find((r) => r.namespace === namespace && r.name === name)?.cluster?.spec?.bootstrap?.recovery && (
|
|
160
|
+
<CNPGRestoreValidation namespace={namespace} name={name} />
|
|
161
|
+
)}
|
|
162
|
+
<CNPGProtection
|
|
163
|
+
data={data}
|
|
164
|
+
fleet={readyFleet}
|
|
165
|
+
namespaces={[namespace]}
|
|
166
|
+
searchParams={searchParams}
|
|
167
|
+
onSetParams={() => {}}
|
|
168
|
+
onInspect={onInspect}
|
|
169
|
+
inspected={null}
|
|
170
|
+
onClearNamespaces={() => {}}
|
|
171
|
+
scopeCluster={{ namespace, name }}
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
174
|
+
)
|
|
175
|
+
}}
|
|
176
|
+
</CNPGScreenGate>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function CNPGInvestigationAction({ namespace, name, render, context }: {
|
|
181
|
+
namespace: string; name: string; render: RenderDiagnoseAction; context: Parameters<RenderDiagnoseAction>[0]
|
|
182
|
+
}): ReactNode {
|
|
183
|
+
const { row, dimensions } = useCNPGClusterAssessment(namespace, name)
|
|
184
|
+
const problem = !!row?.problems.length || dimensions?.some((d) => ['degraded', 'alert', 'unhealthy'].includes(d.tone))
|
|
185
|
+
const assessed = !!row && !!dimensions && dimensions.every((d) => d.tone !== 'unknown')
|
|
186
|
+
return render({ ...context, health: problem ? 'problem' : assessed ? 'healthy' : 'unknown' })
|
|
187
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SelectedResource } from '../../types'
|
|
2
|
+
import { CNPGOverview } from './CNPGOverview'
|
|
3
|
+
import { CNPGScreenGate } from './shared'
|
|
4
|
+
import { useCNPGFleet } from './useCNPGSidebarWorkspace'
|
|
5
|
+
import { useCNPGScreenParams } from './useCNPGScreenParams'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The CloudNativePG Cluster kind's list on its Resources route: the Clusters
|
|
9
|
+
* view of /cnpg, over the same data and URL parameters, inspecting rows in the
|
|
10
|
+
* Resources page's drawer.
|
|
11
|
+
*/
|
|
12
|
+
export function CNPGClustersKindView({
|
|
13
|
+
namespaces,
|
|
14
|
+
inspected,
|
|
15
|
+
onInspect,
|
|
16
|
+
onClearNamespaces,
|
|
17
|
+
onCreate,
|
|
18
|
+
}: {
|
|
19
|
+
namespaces: string[]
|
|
20
|
+
inspected: SelectedResource | null
|
|
21
|
+
onInspect: (resource: SelectedResource) => void
|
|
22
|
+
onClearNamespaces: () => void
|
|
23
|
+
onCreate: () => void
|
|
24
|
+
}) {
|
|
25
|
+
const { query, fleet } = useCNPGFleet(namespaces)
|
|
26
|
+
const [searchParams, setParams] = useCNPGScreenParams()
|
|
27
|
+
return (
|
|
28
|
+
<div className="flex min-h-0 min-w-0 flex-1 flex-col bg-theme-base">
|
|
29
|
+
<CNPGScreenGate query={query} fleet={fleet}>
|
|
30
|
+
{(data, readyFleet) => (
|
|
31
|
+
<CNPGOverview
|
|
32
|
+
data={data}
|
|
33
|
+
fleet={readyFleet}
|
|
34
|
+
namespaces={namespaces}
|
|
35
|
+
searchParams={searchParams}
|
|
36
|
+
onSetParams={setParams}
|
|
37
|
+
onInspect={onInspect}
|
|
38
|
+
inspected={inspected}
|
|
39
|
+
onClearNamespaces={onClearNamespaces}
|
|
40
|
+
onCreate={onCreate}
|
|
41
|
+
/>
|
|
42
|
+
)}
|
|
43
|
+
</CNPGScreenGate>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|