@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
package/src/api/cnpg.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query'
|
|
2
|
-
import type { CNPGWorkspaceResponse, TimelineEvent } from '@skyhook-io/k8s-ui'
|
|
3
|
-
import { fetchJSON } from './client'
|
|
1
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
2
|
+
import type { CNPGKindCoverage, CNPGSchedulePreview, CNPGWorkspaceResponse, Grant, TimelineEvent } from '@skyhook-io/k8s-ui'
|
|
3
|
+
import { fetchJSON, useRadarFeature } from './client'
|
|
4
|
+
import { apiUrl } from './config'
|
|
5
|
+
import { useReviewedActionMutation } from './reviewedActionMutation'
|
|
6
|
+
import { shouldRetryRadarQuery } from './radarFeatures'
|
|
7
|
+
import type { CNPGOperatorDiagnosis } from './cnpg-recovery'
|
|
8
|
+
import type { CNPGPoolerFacts } from './cnpg-sessions'
|
|
9
|
+
import { actionErrorCode, type ActionCapability, type ActionErrorCode, type ActionRequest } from './actions'
|
|
4
10
|
|
|
5
11
|
// /api/cnpg/workspace
|
|
6
12
|
//
|
|
@@ -11,19 +17,18 @@ import { fetchJSON } from './client'
|
|
|
11
17
|
export function useCNPGWorkspace(namespaces: string[], options?: { enabled?: boolean }) {
|
|
12
18
|
const ns = [...namespaces].sort().join(',')
|
|
13
19
|
return useQuery<CNPGWorkspaceResponse>({
|
|
14
|
-
queryKey: ['cnpg', 'workspace', ns],
|
|
20
|
+
queryKey: ['cnpg', 'workspace', ns ],
|
|
15
21
|
queryFn: ({ signal }) => fetchJSON<CNPGWorkspaceResponse>(`/cnpg/workspace${ns ? `?namespaces=${encodeURIComponent(ns)}` : ''}`, signal),
|
|
16
22
|
enabled: options?.enabled ?? true,
|
|
17
23
|
staleTime: 10_000,
|
|
18
24
|
refetchInterval: 30_000,
|
|
19
25
|
placeholderData: (prev) => prev,
|
|
26
|
+
retry: shouldRetryRadarQuery,
|
|
20
27
|
})
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
deniedNamespaces?: string[]
|
|
26
|
-
}
|
|
30
|
+
/** The operator's Deployments and Services are read with the same per-kind coverage as the workspace. */
|
|
31
|
+
export type CNPGOperatorCoverage = CNPGKindCoverage
|
|
27
32
|
|
|
28
33
|
export interface CNPGOperatorComponent {
|
|
29
34
|
role: 'operator' | 'plugin'
|
|
@@ -34,6 +39,26 @@ export interface CNPGOperatorComponent {
|
|
|
34
39
|
version?: string
|
|
35
40
|
readyReplicas: number | null
|
|
36
41
|
replicas: number | null
|
|
42
|
+
/** The Deployment's Pods; null when they could not be read (see podCoverage). */
|
|
43
|
+
pods?: CNPGOperatorComponentPod[] | null
|
|
44
|
+
podCoverage?: { state: string; grant?: Grant; reason?: string }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface CNPGContainerTermination {
|
|
48
|
+
container: string
|
|
49
|
+
reason: string
|
|
50
|
+
exitCode: number
|
|
51
|
+
finishedAt?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface CNPGOperatorComponentPod {
|
|
55
|
+
name: string
|
|
56
|
+
ready: boolean
|
|
57
|
+
/** Restarts across the Pod's containers since the Pod was created. */
|
|
58
|
+
restarts: number
|
|
59
|
+
/** When the current container started. */
|
|
60
|
+
startedAt?: string
|
|
61
|
+
lastTermination?: CNPGContainerTermination
|
|
37
62
|
}
|
|
38
63
|
|
|
39
64
|
export interface CNPGOperatorConfig {
|
|
@@ -51,6 +76,7 @@ export interface CNPGOperatorResponse {
|
|
|
51
76
|
coverage: { deployments: CNPGOperatorCoverage; services: CNPGOperatorCoverage }
|
|
52
77
|
components: CNPGOperatorComponent[]
|
|
53
78
|
config: CNPGOperatorConfig[]
|
|
79
|
+
diagnosis?: CNPGOperatorDiagnosis[]
|
|
54
80
|
}
|
|
55
81
|
|
|
56
82
|
// /api/cnpg/operator
|
|
@@ -60,11 +86,43 @@ export interface CNPGOperatorResponse {
|
|
|
60
86
|
// its own namespace, which users rarely have selected.
|
|
61
87
|
export function useCNPGOperator(options?: { enabled?: boolean }) {
|
|
62
88
|
return useQuery<CNPGOperatorResponse>({
|
|
63
|
-
queryKey: ['cnpg', 'operator'],
|
|
89
|
+
queryKey: ['cnpg', 'operator' ],
|
|
64
90
|
queryFn: ({ signal }) => fetchJSON<CNPGOperatorResponse>('/cnpg/operator', signal),
|
|
65
91
|
enabled: options?.enabled ?? true,
|
|
66
92
|
staleTime: 30_000,
|
|
67
93
|
refetchInterval: 60_000,
|
|
94
|
+
retry: shouldRetryRadarQuery,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Whether the operator watching a namespace is acting on it. While it is not,
|
|
100
|
+
* CNPG status there is whatever the operator last wrote. webhookRejects is
|
|
101
|
+
* null when the webhook state could not be read.
|
|
102
|
+
*/
|
|
103
|
+
export interface CNPGOperatorVerdict {
|
|
104
|
+
state: 'reconciling' | 'notReconciling' | 'unknown' | 'notWatched'
|
|
105
|
+
reasons?: string[]
|
|
106
|
+
unknown?: string
|
|
107
|
+
webhookRejects: boolean | null
|
|
108
|
+
webhookReason?: string
|
|
109
|
+
operator?: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// /api/cnpg/operator/status?namespaces=
|
|
113
|
+
//
|
|
114
|
+
// The cheap operator verdict for the fleet and cluster pages, keyed by namespace.
|
|
115
|
+
export function useCNPGOperatorStatus(namespaces: string[], options?: { enabled?: boolean }) {
|
|
116
|
+
const { guard, gatedKey } = useRadarFeature('cnpgWorkspace')
|
|
117
|
+
const ns = [...new Set(namespaces)].sort().join(',')
|
|
118
|
+
return useQuery<{ namespaces: Record<string, CNPGOperatorVerdict> }>({
|
|
119
|
+
queryKey: ['cnpg', 'operator-status', ns, ...gatedKey],
|
|
120
|
+
queryFn: ({ signal }) => guard(() => fetchJSON<{ namespaces: Record<string, CNPGOperatorVerdict> }>(`/cnpg/operator/status?namespaces=${encodeURIComponent(ns)}`, signal)),
|
|
121
|
+
enabled: (options?.enabled ?? true) && ns !== '',
|
|
122
|
+
staleTime: 10_000,
|
|
123
|
+
refetchInterval: 30_000,
|
|
124
|
+
placeholderData: (prev) => prev,
|
|
125
|
+
retry: shouldRetryRadarQuery,
|
|
68
126
|
})
|
|
69
127
|
}
|
|
70
128
|
|
|
@@ -81,7 +139,7 @@ export interface CNPGClusterActivityResponse {
|
|
|
81
139
|
// attributed to it, including ones since deleted.
|
|
82
140
|
export function useCNPGClusterActivity(namespace: string, name: string, sinceHours = 24) {
|
|
83
141
|
return useQuery<CNPGClusterActivityResponse>({
|
|
84
|
-
queryKey: ['cnpg', 'activity', namespace, name, sinceHours],
|
|
142
|
+
queryKey: ['cnpg', 'activity', namespace, name, sinceHours ],
|
|
85
143
|
queryFn: ({ signal }) => {
|
|
86
144
|
const since = new Date(Date.now() - sinceHours * 3600_000).toISOString()
|
|
87
145
|
return fetchJSON<CNPGClusterActivityResponse>(
|
|
@@ -91,5 +149,365 @@ export function useCNPGClusterActivity(namespace: string, name: string, sinceHou
|
|
|
91
149
|
},
|
|
92
150
|
staleTime: 10_000,
|
|
93
151
|
refetchInterval: 30_000,
|
|
152
|
+
retry: shouldRetryRadarQuery,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface CNPGInstanceFact {
|
|
157
|
+
pod: string
|
|
158
|
+
podUID: string
|
|
159
|
+
role: 'primary' | 'standby'
|
|
160
|
+
ready: boolean
|
|
161
|
+
healthy: boolean
|
|
162
|
+
fenced: boolean
|
|
163
|
+
podReadable: boolean
|
|
164
|
+
podExists: boolean
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface CNPGBackupMethod {
|
|
168
|
+
method: 'plugin' | 'volumeSnapshot' | 'barmanObjectStore'
|
|
169
|
+
pluginName?: string
|
|
170
|
+
capability: 'backup' | 'unknown' | 'none'
|
|
171
|
+
reason?: string
|
|
172
|
+
deprecated?: boolean
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface CNPGClusterFacts {
|
|
176
|
+
generation: number
|
|
177
|
+
currentPrimary?: string
|
|
178
|
+
targetPrimary?: string
|
|
179
|
+
phase?: string
|
|
180
|
+
phaseReason?: string
|
|
181
|
+
hibernation: string
|
|
182
|
+
hibernated: boolean
|
|
183
|
+
fencedInstances: { raw: string; all: boolean; instances: string[]; malformed?: boolean }
|
|
184
|
+
instances: CNPGInstanceFact[]
|
|
185
|
+
backupMethods: CNPGBackupMethod[]
|
|
186
|
+
backupTarget?: string
|
|
187
|
+
/** The ContinuousArchiving condition is False. */
|
|
188
|
+
archivingFailing: boolean
|
|
189
|
+
isReplicaCluster: boolean
|
|
190
|
+
maintenance: { declared: boolean; inProgress: boolean; reusePVC: boolean }
|
|
191
|
+
terminating: boolean
|
|
192
|
+
/** A running offline volume-snapshot Backup: the operator fenced its instance and lifts the fence itself. */
|
|
193
|
+
coldSnapshotBackup?: string
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export type CNPGClusterActionName =
|
|
197
|
+
| 'backup'
|
|
198
|
+
| 'switchover'
|
|
199
|
+
| 'restart'
|
|
200
|
+
| 'restartInstance'
|
|
201
|
+
| 'reload'
|
|
202
|
+
| 'fence'
|
|
203
|
+
| 'unfence'
|
|
204
|
+
| 'hibernate'
|
|
205
|
+
| 'rehydrate'
|
|
206
|
+
| 'setMaintenance'
|
|
207
|
+
| 'unsetMaintenance'
|
|
208
|
+
|
|
209
|
+
export interface CNPGClusterCapabilities {
|
|
210
|
+
uid: string
|
|
211
|
+
resourceVersion: string
|
|
212
|
+
context: string
|
|
213
|
+
facts: CNPGClusterFacts
|
|
214
|
+
/** `restore` is creating a new Cluster in this namespace from this one's backups (`create clusters`). */
|
|
215
|
+
actions: Record<CNPGClusterActionName, ActionCapability> & { psql: ActionCapability; destroyInstance: ActionCapability; restore: ActionCapability }
|
|
216
|
+
instanceActions: Record<string, { restart: ActionCapability; switchoverTarget: ActionCapability; fence: ActionCapability; unfence: ActionCapability; psql: ActionCapability; destroy: ActionCapability }>
|
|
217
|
+
restartPlan?: {
|
|
218
|
+
primaryUpdateStrategy?: string
|
|
219
|
+
primaryUpdateMethod?: string
|
|
220
|
+
steps: { instance: string; role: string; effect: 'recreate' | 'skipped_fenced' | 'switchover' | 'restart' | 'wait_for_user' | 'restart_only_instance' }[]
|
|
221
|
+
}
|
|
222
|
+
hibernateEffects?: {
|
|
223
|
+
poolers: CNPGEffectList
|
|
224
|
+
unsuspendedScheduledBackups: CNPGEffectList
|
|
225
|
+
databases: CNPGEffectList
|
|
226
|
+
publications: CNPGEffectList
|
|
227
|
+
subscriptions: CNPGEffectList
|
|
228
|
+
volumes: { available: boolean; reason?: string; items: { name: string; instance: string; role: string; capacity?: string; requested?: string }[] }
|
|
229
|
+
}
|
|
230
|
+
operator?: CNPGOperatorVerdict
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface CNPGEffectList {
|
|
234
|
+
available: boolean
|
|
235
|
+
reason?: string
|
|
236
|
+
names: string[]
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export type CNPGScheduleActionName = 'suspend' | 'resume' | 'run' | 'setSchedule'
|
|
240
|
+
|
|
241
|
+
export interface CNPGScheduleCapabilities {
|
|
242
|
+
uid: string
|
|
243
|
+
resourceVersion: string
|
|
244
|
+
context: string
|
|
245
|
+
facts: {
|
|
246
|
+
generation: number
|
|
247
|
+
cluster: string
|
|
248
|
+
clusterUID: string
|
|
249
|
+
clusterGeneration: number
|
|
250
|
+
suspended: boolean
|
|
251
|
+
nextScheduleTime?: string
|
|
252
|
+
method?: string
|
|
253
|
+
pluginName?: string
|
|
254
|
+
target?: string
|
|
255
|
+
clusterState: 'ok' | 'missing' | 'hibernated' | 'unreadable'
|
|
256
|
+
backupBlockedReason?: string
|
|
257
|
+
terminating: boolean
|
|
258
|
+
catchUp: boolean
|
|
259
|
+
/** spec.schedule verbatim; setSchedule binds it. */
|
|
260
|
+
schedule: string
|
|
261
|
+
preview: CNPGSchedulePreview
|
|
262
|
+
}
|
|
263
|
+
actions: Record<CNPGScheduleActionName, ActionCapability>
|
|
264
|
+
operator?: CNPGOperatorVerdict
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function cnpgPath(kind: 'clusters' | 'scheduledbackups' | 'poolers', namespace: string, name: string) {
|
|
268
|
+
return `/cnpg/${kind}/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function useCNPGClusterCapabilities(namespace: string, name: string, enabled = true) {
|
|
272
|
+
const { guard, gatedKey } = useRadarFeature('cnpgWorkspace')
|
|
273
|
+
return useQuery<CNPGClusterCapabilities>({
|
|
274
|
+
queryKey: ['cnpg', 'capabilities', 'clusters', namespace, name, ...gatedKey],
|
|
275
|
+
queryFn: ({ signal }) => guard(() => fetchJSON<CNPGClusterCapabilities>(`${cnpgPath('clusters', namespace, name)}/capabilities`, signal)),
|
|
276
|
+
enabled: enabled && !!name,
|
|
277
|
+
staleTime: 5_000,
|
|
278
|
+
retry: false,
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Parsed server-side with the operator's cron semantics; runs counted from the
|
|
283
|
+
// schedule's own lastCheckTime. Pure computation: writes nothing.
|
|
284
|
+
export function useCNPGSchedulePreview(namespace: string, name: string, schedule: string, enabled = true) {
|
|
285
|
+
const { guard, gatedKey } = useRadarFeature('cnpgWorkspace')
|
|
286
|
+
return useQuery<CNPGSchedulePreview>({
|
|
287
|
+
queryKey: ['cnpg', 'schedule-preview', namespace, name, schedule, ...gatedKey],
|
|
288
|
+
queryFn: ({ signal }) => guard(() =>
|
|
289
|
+
fetchJSON<CNPGSchedulePreview>(`${cnpgPath('scheduledbackups', namespace, name)}/schedule-preview?schedule=${encodeURIComponent(schedule)}`, signal)),
|
|
290
|
+
enabled: enabled && !!name,
|
|
291
|
+
staleTime: 30_000,
|
|
292
|
+
retry: false,
|
|
293
|
+
placeholderData: (prev) => prev,
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function useCNPGScheduleCapabilities(namespace: string, name: string, enabled = true) {
|
|
298
|
+
const { guard, gatedKey } = useRadarFeature('cnpgWorkspace')
|
|
299
|
+
return useQuery<CNPGScheduleCapabilities>({
|
|
300
|
+
queryKey: ['cnpg', 'capabilities', 'scheduledbackups', namespace, name, ...gatedKey],
|
|
301
|
+
queryFn: ({ signal }) => guard(() => fetchJSON<CNPGScheduleCapabilities>(`${cnpgPath('scheduledbackups', namespace, name)}/capabilities`, signal)),
|
|
302
|
+
enabled: enabled && !!name,
|
|
303
|
+
staleTime: 5_000,
|
|
304
|
+
retry: false,
|
|
305
|
+
})
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export interface CNPGActionResult {
|
|
309
|
+
action: string
|
|
310
|
+
message: string
|
|
311
|
+
backup?: string
|
|
312
|
+
resolvedAfterTimeout?: boolean
|
|
313
|
+
catchUp?: boolean
|
|
314
|
+
/** What the action acted on, for following its outcome. */
|
|
315
|
+
target?: { pod?: string; podUID?: string; pid?: number; backendStart?: string; keepPVC?: boolean; pvcs?: { name: string; uid: string }[]; jobs?: string[]; paused?: boolean; generation?: number }
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// No mutation meta: errors stay with the dialog (shown inline so the user can
|
|
319
|
+
// adjust and retry), and the caller toasts success worded from the result.
|
|
320
|
+
type CNPGActionMutation = {
|
|
321
|
+
clusters:
|
|
322
|
+
| { action: CNPGClusterActionName | 'destroyInstance'; request: ActionRequest<CNPGClusterFacts> }
|
|
323
|
+
| { action: 'cancelBackend' | 'terminateBackend'; request: ActionRequest<Record<string, never>, { pod: string; podUID?: string; pid: number; backendStart: string }> }
|
|
324
|
+
scheduledbackups: { action: CNPGScheduleActionName; request: ActionRequest<CNPGScheduleCapabilities['facts']> }
|
|
325
|
+
poolers: { action: 'pause' | 'resume'; request: ActionRequest<Pick<CNPGPoolerFacts, 'paused' | 'generation'>> }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function useCNPGAction<Kind extends keyof CNPGActionMutation>(kind: Kind, namespace: string, name: string, reviewedContext: string | undefined) {
|
|
329
|
+
const { guard } = useRadarFeature('cnpgWorkspace')
|
|
330
|
+
const queryClient = useQueryClient()
|
|
331
|
+
return useReviewedActionMutation<CNPGActionResult, CNPGActionMutation[Kind]>(apiUrl(cnpgPath(kind, namespace, name)), reviewedContext, {
|
|
332
|
+
mutationFn: ({ action, request }) => guard(() =>
|
|
333
|
+
fetchJSON<CNPGActionResult>(`${cnpgPath(kind, namespace, name)}/actions/${action}`, {
|
|
334
|
+
method: 'POST',
|
|
335
|
+
headers: { 'Content-Type': 'application/json' },
|
|
336
|
+
body: JSON.stringify(request),
|
|
337
|
+
})),
|
|
338
|
+
onSuccess: () => {
|
|
339
|
+
queryClient.invalidateQueries({ queryKey: ['cnpg'] })
|
|
340
|
+
queryClient.invalidateQueries({ queryKey: ['resource'] })
|
|
341
|
+
},
|
|
342
|
+
// A refusal over changed facts re-reads them, so the dialog shows what
|
|
343
|
+
// the user would now be confirming.
|
|
344
|
+
onError: (err) => {
|
|
345
|
+
if (cnpgActionErrorCode(err) !== undefined) queryClient.invalidateQueries({ queryKey: ['cnpg'] })
|
|
346
|
+
},
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** The action refusals the CloudNativePG handlers add to the shared ones. */
|
|
351
|
+
export type CNPGActionErrorCode = ActionErrorCode | 'all_fenced' | 'operator_webhook_unavailable' | 'invalid_schedule'
|
|
352
|
+
|
|
353
|
+
export function cnpgActionErrorCode(err: unknown): CNPGActionErrorCode | undefined {
|
|
354
|
+
return actionErrorCode<CNPGActionErrorCode>(err)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export type CNPGRuntimeSourceState = 'ok' | 'denied' | 'unreachable' | 'error' | 'partial'
|
|
358
|
+
|
|
359
|
+
export interface CNPGRuntimeReplication {
|
|
360
|
+
applicationName: string
|
|
361
|
+
state?: string
|
|
362
|
+
syncState?: string
|
|
363
|
+
syncPriority?: number
|
|
364
|
+
writeLag?: number
|
|
365
|
+
flushLag?: number
|
|
366
|
+
replayLag?: number
|
|
367
|
+
sentLsn?: string
|
|
368
|
+
writeLsn?: string
|
|
369
|
+
flushLsn?: string
|
|
370
|
+
replayLsn?: string
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export interface CNPGRuntimeBaseBackup {
|
|
374
|
+
applicationName: string
|
|
375
|
+
instance?: string
|
|
376
|
+
phase: string
|
|
377
|
+
startedAt?: string
|
|
378
|
+
/** Absent while PostgreSQL has no estimate (waiting for a checkpoint, or estimation disabled). */
|
|
379
|
+
totalBytes?: number
|
|
380
|
+
streamedBytes: number
|
|
381
|
+
tablespacesTotal: number
|
|
382
|
+
tablespacesStreamed: number
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export interface CNPGRuntimeInstance {
|
|
386
|
+
pod: string
|
|
387
|
+
podUID?: string
|
|
388
|
+
role: 'primary' | 'replica' | 'unknown'
|
|
389
|
+
status: {
|
|
390
|
+
state: CNPGRuntimeSourceState
|
|
391
|
+
error?: string
|
|
392
|
+
reason?: string
|
|
393
|
+
capturedAt?: string
|
|
394
|
+
isPrimary?: boolean
|
|
395
|
+
currentLsn?: string
|
|
396
|
+
receivedLsn?: string
|
|
397
|
+
replayLsn?: string
|
|
398
|
+
timeline?: number
|
|
399
|
+
replayPaused?: boolean
|
|
400
|
+
pendingRestart?: boolean
|
|
401
|
+
pendingRestartForDecrease?: boolean
|
|
402
|
+
isPgRewindRunning?: boolean
|
|
403
|
+
instanceManagerVersion?: string
|
|
404
|
+
roleDetail?: 'primary' | 'pgRewind' | 'replayPaused' | 'streaming' | 'fileBased'
|
|
405
|
+
isWalReceiverActive?: boolean
|
|
406
|
+
archiving?: { lastArchivedWal?: string; lastArchivedAt?: string; lastFailedWal?: string; lastFailedAt?: string; readyWalFiles?: number }
|
|
407
|
+
replication?: CNPGRuntimeReplication[] | null
|
|
408
|
+
slots?: null | { name: string; type?: string; plugin?: string; active?: boolean; database?: string; restartLsn?: string; walStatus?: string; retainedBytes?: number }[]
|
|
409
|
+
slotsTruncated?: boolean
|
|
410
|
+
/** pg_basebackup streams to joining instances; [] when the report says none run. */
|
|
411
|
+
baseBackups?: CNPGRuntimeBaseBackup[] | null
|
|
412
|
+
/**
|
|
413
|
+
* The instance manager answered without finishing its reads (a masked
|
|
414
|
+
* error, or pg_rewind running): replication, slots, baseBackups and
|
|
415
|
+
* archiving are null when unread, and pendingRestart is not established.
|
|
416
|
+
*/
|
|
417
|
+
incomplete?: boolean
|
|
418
|
+
maskedError?: string
|
|
419
|
+
}
|
|
420
|
+
metrics: {
|
|
421
|
+
state: CNPGRuntimeSourceState
|
|
422
|
+
error?: string
|
|
423
|
+
reason?: string
|
|
424
|
+
capturedAt?: string
|
|
425
|
+
missing?: string[]
|
|
426
|
+
maxConnections?: number
|
|
427
|
+
sessions?: { state: string; database: string; user: string; application: string; count: number }[]
|
|
428
|
+
sessionsTotal?: number
|
|
429
|
+
waitingBackends?: number
|
|
430
|
+
oldestXactSeconds?: number
|
|
431
|
+
xidAge?: { database: string; age: number }[]
|
|
432
|
+
mxidAge?: { database: string; age: number }[]
|
|
433
|
+
/** null when cnpg_pg_extensions_update_available was not exported (see missing); [] when all are current. */
|
|
434
|
+
extensionUpdates?: { database: string; extension: string; installedVersion: string; defaultVersion: string }[] | null
|
|
435
|
+
databaseSizes?: { database: string; bytes: number }[]
|
|
436
|
+
archiver?: { archivedCount?: number; failedCount?: number; secondsSinceLastArchival?: number; secondsSinceLastFailure?: number }
|
|
437
|
+
xactCommitTotal?: number
|
|
438
|
+
xactRollbackTotal?: number
|
|
439
|
+
blksHit?: number
|
|
440
|
+
blksRead?: number
|
|
441
|
+
deadlocksTotal?: number
|
|
442
|
+
tempBytesTotal?: number
|
|
443
|
+
walBytes?: number
|
|
444
|
+
/** cnpg_last_update_timestamp: when the exporter last ran its queries (epoch seconds). */
|
|
445
|
+
lastUpdateTimestamp?: number
|
|
446
|
+
/** Client sessions per state over every group (absent when there are none). */
|
|
447
|
+
sessionsByState?: Record<string, number>
|
|
448
|
+
walSegments?: number
|
|
449
|
+
postmasterStartTime?: number
|
|
450
|
+
/** pg_stat_database counters per database, cumulative since the last stats reset. */
|
|
451
|
+
databases?: { database: string; xactCommit?: number; xactRollback?: number; tempFiles?: number; tempBytes?: number; deadlocks?: number; blksHit?: number; blksRead?: number }[]
|
|
452
|
+
/** Absent when neither pg_stat_checkpointer (17+) nor pg_stat_bgwriter was exported. */
|
|
453
|
+
checkpoints?: {
|
|
454
|
+
source: 'pg_stat_checkpointer' | 'pg_stat_bgwriter'
|
|
455
|
+
timed?: number
|
|
456
|
+
requested?: number
|
|
457
|
+
restartpointsTimed?: number
|
|
458
|
+
restartpointsRequested?: number
|
|
459
|
+
restartpointsDone?: number
|
|
460
|
+
buffersWritten?: number
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export interface CNPGRuntimeResponse {
|
|
466
|
+
cluster: { namespace: string; name: string; uid: string }
|
|
467
|
+
sampledAt: string
|
|
468
|
+
permission: { proxy: 'allowed' | 'denied'; grant?: Grant }
|
|
469
|
+
instances: CNPGRuntimeInstance[]
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// /api/cnpg/clusters/{ns}/{name}/runtime — live instance-manager status and
|
|
473
|
+
// exporter metrics read through pods/proxy. Polled only while visible.
|
|
474
|
+
export function useCNPGRuntime(namespace: string, name: string, enabled = true) {
|
|
475
|
+
const { guard, gatedKey } = useRadarFeature('cnpgWorkspace')
|
|
476
|
+
return useQuery<CNPGRuntimeResponse>({
|
|
477
|
+
queryKey: ['cnpg', 'runtime', namespace, name, ...gatedKey],
|
|
478
|
+
queryFn: ({ signal }) => guard(() => fetchJSON<CNPGRuntimeResponse>(`/cnpg/clusters/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/runtime`, signal)),
|
|
479
|
+
enabled: enabled && !!name,
|
|
480
|
+
refetchInterval: (q) => (q.state.data?.permission.proxy === 'denied' ? false : 5_000),
|
|
481
|
+
refetchIntervalInBackground: false,
|
|
482
|
+
staleTime: 4_000,
|
|
483
|
+
retry: false,
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export interface CNPGPoolerRuntimeResponse {
|
|
488
|
+
pooler: { namespace: string; name: string; uid: string }
|
|
489
|
+
sampledAt: string
|
|
490
|
+
permission: { proxy: 'allowed' | 'denied'; grant?: Grant }
|
|
491
|
+
pods: {
|
|
492
|
+
pod: string
|
|
493
|
+
schedulingReason?: string
|
|
494
|
+
state: CNPGRuntimeSourceState
|
|
495
|
+
error?: string
|
|
496
|
+
reason?: string
|
|
497
|
+
missing?: string[]
|
|
498
|
+
pools?: { database: string; user: string; clActive?: number; clWaiting?: number; svActive?: number; svIdle?: number; svUsed?: number; maxwaitSeconds?: number; poolMode?: string }[]
|
|
499
|
+
}[]
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export function useCNPGPoolerRuntime(namespace: string, name: string, enabled = true) {
|
|
503
|
+
const { guard, gatedKey } = useRadarFeature('cnpgWorkspace')
|
|
504
|
+
return useQuery<CNPGPoolerRuntimeResponse>({
|
|
505
|
+
queryKey: ['cnpg', 'pooler-runtime', namespace, name, ...gatedKey],
|
|
506
|
+
queryFn: ({ signal }) => guard(() => fetchJSON<CNPGPoolerRuntimeResponse>(`/cnpg/poolers/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/runtime`, signal)),
|
|
507
|
+
enabled: enabled && !!name,
|
|
508
|
+
refetchInterval: 30_000,
|
|
509
|
+
refetchIntervalInBackground: false,
|
|
510
|
+
staleTime: 25_000,
|
|
511
|
+
retry: false,
|
|
94
512
|
})
|
|
95
513
|
}
|
|
@@ -4,7 +4,7 @@ import { createRoot } from 'react-dom/client'
|
|
|
4
4
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
5
5
|
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
|
6
6
|
import { getRadarUpgradeRequirement } from '@skyhook-io/k8s-ui'
|
|
7
|
-
import { ApiError, isNotFoundError, useApplications, useCapacityPoolDetail, useDrainPlan, useNodeRemovalPlan, usePodEnvironment, useWorkloadHistory } from './client'
|
|
7
|
+
import { ApiError, isNotFoundError, useApplications, useCapacityPoolDetail, useDrainPlan, useNodeRemovalPlan, usePodEnvironment, useSubjectIssues, useWorkloadHistory } from './client'
|
|
8
8
|
import { getApiBase } from './config'
|
|
9
9
|
import { usePolicyResource } from './policy'
|
|
10
10
|
import { isRadarFeatureUnsupported } from './radarFeatures'
|
|
@@ -35,6 +35,7 @@ beforeEach(() => {
|
|
|
35
35
|
if (url.includes('/capacity/pools/')) return Promise.resolve(Response.json({ pool: { name: 'default' } }))
|
|
36
36
|
if (url.endsWith('/environment')) return Promise.resolve(Response.json({ containers: [], coverage: {} }))
|
|
37
37
|
if (url.includes('/policy/resource/')) return Promise.resolve(Response.json({ evaluated: true, status: 'ready' }))
|
|
38
|
+
if (url.includes('/issues/resource/')) return Promise.resolve(Response.json({ issues: [], coverage: 'ok' }))
|
|
38
39
|
return new Promise<Response>(() => {})
|
|
39
40
|
}))
|
|
40
41
|
element = document.createElement('div')
|
|
@@ -73,6 +74,49 @@ function Policy() {
|
|
|
73
74
|
return <span>{data?.status ?? (getRadarUpgradeRequirement(error) ? 'upgrade' : 'pending')}</span>
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
function SubjectIssues() {
|
|
78
|
+
const { data, error } = useSubjectIssues({ kind: 'Pod', group: '', namespace: 'shop', name: 'web' }, true)
|
|
79
|
+
return <span>{data ? 'data' : getRadarUpgradeRequirement(error) ? 'upgrade' : error ? 'unconfirmed' : 'pending'}</span>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
it('waits for coverage capabilities instead of probing an older array-shaped issues route', async () => {
|
|
83
|
+
await render(<SubjectIssues />)
|
|
84
|
+
expect(element.textContent).toBe('pending')
|
|
85
|
+
expect(asked('/issues/resource/')).toBe(false)
|
|
86
|
+
|
|
87
|
+
await act(async () => { client.setQueryData(['capabilities'], { features: { resourceIssues: true } }) })
|
|
88
|
+
await settle()
|
|
89
|
+
await settle()
|
|
90
|
+
expect(element.textContent).toBe('upgrade')
|
|
91
|
+
expect(asked('/issues/resource/')).toBe(false)
|
|
92
|
+
|
|
93
|
+
await act(async () => { client.setQueryData(['capabilities'], { features: { resourceIssueCoverage: true } }) })
|
|
94
|
+
await settle()
|
|
95
|
+
await settle()
|
|
96
|
+
expect(element.textContent).toBe('data')
|
|
97
|
+
expect(asked('/issues/resource/')).toBe(true)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('leaves issue coverage unconfirmed after a failed capability check and recovers when it succeeds', async () => {
|
|
101
|
+
const answer = vi.mocked(fetch).getMockImplementation()!
|
|
102
|
+
vi.mocked(fetch).mockImplementation((input, init) =>
|
|
103
|
+
String(input).endsWith('/capabilities')
|
|
104
|
+
? Promise.resolve(Response.json({ error: 'capabilities unavailable' }, { status: 503 }))
|
|
105
|
+
: answer(input, init),
|
|
106
|
+
)
|
|
107
|
+
await render(<SubjectIssues />)
|
|
108
|
+
await settle()
|
|
109
|
+
await settle()
|
|
110
|
+
expect(element.textContent).toBe('unconfirmed')
|
|
111
|
+
expect(asked('/issues/resource/')).toBe(false)
|
|
112
|
+
|
|
113
|
+
await act(async () => { client.setQueryData(['capabilities'], { features: { resourceIssueCoverage: true } }) })
|
|
114
|
+
await settle()
|
|
115
|
+
await settle()
|
|
116
|
+
expect(element.textContent).toBe('data')
|
|
117
|
+
expect(asked('/issues/resource/')).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
|
|
76
120
|
it('fetches for real once the agent turns out to advertise the feature', async () => {
|
|
77
121
|
await render(<Environment />)
|
|
78
122
|
expect(element.textContent).toBe('upgrade')
|
|
@@ -90,14 +134,14 @@ it('turns an unsupported policy answer into not installed once discovery shows n
|
|
|
90
134
|
// No upgrade note yet: discovery may still withdraw it.
|
|
91
135
|
expect(element.textContent).toBe('loading')
|
|
92
136
|
|
|
93
|
-
await act(async () => { client.setQueryData(['api-resources'], [{ group: '', name: 'pods' }]) })
|
|
137
|
+
await act(async () => { client.setQueryData(['api-resources'], [{ group: '', version: 'v1', kind: 'Pod', name: 'pods', namespaced: true, isCrd: false, verbs: ['get', 'list', 'watch'] }]) })
|
|
94
138
|
await settle()
|
|
95
139
|
expect(element.textContent).toBe('not_installed')
|
|
96
140
|
expect(asked('/policy/resource/')).toBe(false)
|
|
97
141
|
})
|
|
98
142
|
|
|
99
143
|
it('keeps the upgrade note when discovery shows an OpenReports policy engine', async () => {
|
|
100
|
-
client.setQueryData(['api-resources'], [{ group: 'openreports.io', name: 'reports' }])
|
|
144
|
+
client.setQueryData(['api-resources'], [{ group: 'openreports.io', version: 'v1alpha1', kind: 'Report', name: 'reports', namespaced: true, isCrd: true, verbs: ['get', 'list', 'watch'] }])
|
|
101
145
|
await render(<Policy />)
|
|
102
146
|
expect(element.textContent).toBe('upgrade')
|
|
103
147
|
})
|
package/src/api/radarFeatures.ts
CHANGED
|
@@ -28,6 +28,7 @@ export interface RadarFeatureSpec {
|
|
|
28
28
|
|
|
29
29
|
export const RADAR_FEATURES = {
|
|
30
30
|
resourceIssues: { label: 'Operational issues', minimumVersion: 'v1.8.0', flag: 'resourceIssues' },
|
|
31
|
+
resourceIssueCoverage: { label: 'Resource-specific issue views', flag: 'resourceIssueCoverage', flagShippedWithEndpoint: true },
|
|
31
32
|
podEnvironment: { label: 'Values from ConfigMaps and Secrets', minimumVersion: 'v1.9.0', flag: 'podEnvironment' },
|
|
32
33
|
policyResource: { label: 'Policy results', minimumVersion: 'v1.10.0', flag: 'policyResource' },
|
|
33
34
|
capacity: { label: 'Capacity views', minimumVersion: 'v1.9.0' },
|
|
@@ -35,9 +36,11 @@ export const RADAR_FEATURES = {
|
|
|
35
36
|
drainPlan: { label: 'Drain plans', minimumVersion: 'v1.14.0' },
|
|
36
37
|
applications: { label: 'Applications', minimumVersion: 'v1.7.7' },
|
|
37
38
|
workloadHistory: { label: 'Workload history', flag: 'workloadHistory', flagShippedWithEndpoint: true },
|
|
39
|
+
cnpgWorkspace: { label: 'CloudNativePG views', flag: 'cnpgWorkspace', flagShippedWithEndpoint: true },
|
|
40
|
+
cnpgProtectionSetup: { label: 'CloudNativePG backup setup', flag: 'cnpgProtectionSetup', flagShippedWithEndpoint: true },
|
|
41
|
+
gitopsWriteEvidence: { label: 'GitOps revert warnings', flag: 'gitopsWriteEvidence', flagShippedWithEndpoint: true },
|
|
38
42
|
serviceEndpointSlices: { label: 'Service EndpointSlice inventories', flag: 'serviceEndpointSlices', flagShippedWithEndpoint: true },
|
|
39
43
|
trafficRecords: { label: 'Flow records for a graph selection', flag: 'trafficRecords', flagShippedWithEndpoint: true },
|
|
40
|
-
gitopsWriteEvidence: { label: 'GitOps revert warnings', flag: 'gitopsWriteEvidence', flagShippedWithEndpoint: true },
|
|
41
44
|
} as const satisfies Record<string, RadarFeatureSpec>
|
|
42
45
|
|
|
43
46
|
export type RadarFeature = keyof typeof RADAR_FEATURES
|