@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
package/src/api/client.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { canonicalResourceGroup } from '@skyhook-io/k8s-ui/utils/api-resources'
|
|
2
2
|
import { knownKindForPluralWithGroup, pluralToKind } from '@skyhook-io/k8s-ui/utils/navigation'
|
|
3
|
-
import { useEffect, useRef } from 'react'
|
|
3
|
+
import { useEffect, useRef, useCallback, useMemo } from 'react'
|
|
4
4
|
import type { KueueAdmissionResponse } from '@skyhook-io/k8s-ui/types/scheduling'
|
|
5
|
+
import type { GitOpsWriteEvidence } from '@skyhook-io/k8s-ui/utils/gitops-write-guard'
|
|
5
6
|
import type {
|
|
6
7
|
AppHistory,
|
|
7
8
|
AppRow,
|
|
@@ -20,7 +21,7 @@ import type {
|
|
|
20
21
|
YamlDocumentIdentity,
|
|
21
22
|
YamlSchemaLoadResult,
|
|
22
23
|
} from '@skyhook-io/k8s-ui'
|
|
23
|
-
import { useQuery, useMutation, useQueryClient, skipToken } from '@tanstack/react-query'
|
|
24
|
+
import { useQuery, useMutation, useQueryClient, skipToken, type QueryClient } from '@tanstack/react-query'
|
|
24
25
|
import { showApiError, showApiSuccess } from '../components/ui/Toast'
|
|
25
26
|
import { useIsAuthEnabled, useNamespacedCapabilities } from '../contexts/CapabilitiesContext'
|
|
26
27
|
import type {
|
|
@@ -41,6 +42,8 @@ import type {
|
|
|
41
42
|
ResourceDiff,
|
|
42
43
|
UpgradeInfo,
|
|
43
44
|
BatchUpgradeInfo,
|
|
45
|
+
ChartSourceCandidate,
|
|
46
|
+
ChartSourceStatus,
|
|
44
47
|
ValuesDiff,
|
|
45
48
|
ValuesPreviewResponse,
|
|
46
49
|
HelmRepository,
|
|
@@ -228,6 +231,16 @@ export function isNotFoundError(error: unknown): boolean {
|
|
|
228
231
|
return error instanceof ApiError && error.status === 404;
|
|
229
232
|
}
|
|
230
233
|
|
|
234
|
+
/**
|
|
235
|
+
* The server's explanation when it refused a request because the browser
|
|
236
|
+
* reported it came from another site, or undefined for any other error.
|
|
237
|
+
*/
|
|
238
|
+
export function crossOriginRefusalMessage(error: unknown): string | undefined {
|
|
239
|
+
return error instanceof ApiError && error.data?.error_code === "cross_origin_refused"
|
|
240
|
+
? error.message
|
|
241
|
+
: undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
231
244
|
export function isCapacityCursorInvalidError(error: unknown): boolean {
|
|
232
245
|
return (
|
|
233
246
|
error instanceof ApiError &&
|
|
@@ -286,6 +299,20 @@ export function isKindSyncFailed(error: unknown): boolean {
|
|
|
286
299
|
);
|
|
287
300
|
}
|
|
288
301
|
|
|
302
|
+
// timelineDatabaseError returns the driver's error when a timeline read failed
|
|
303
|
+
// because Radar can't connect to its PostgreSQL database, and null for any other
|
|
304
|
+
// failure. The server redacts the password before it sends the error.
|
|
305
|
+
export function timelineDatabaseError(error: unknown): string | null {
|
|
306
|
+
if (
|
|
307
|
+
!(error instanceof ApiError) ||
|
|
308
|
+
error.status !== 503 ||
|
|
309
|
+
error.data?.error_code !== "timeline_database_unreachable"
|
|
310
|
+
)
|
|
311
|
+
return null;
|
|
312
|
+
const detail = error.data.detail;
|
|
313
|
+
return typeof detail === "string" ? detail : "";
|
|
314
|
+
}
|
|
315
|
+
|
|
289
316
|
// SyncKindState / SyncStatusSnapshot mirror k8score's SyncSnapshot — attached
|
|
290
317
|
// to the connection-status payload while the initial informer sync is running.
|
|
291
318
|
export interface SyncKindState {
|
|
@@ -439,7 +466,7 @@ export interface DashboardResourceCounts {
|
|
|
439
466
|
ingresses: number;
|
|
440
467
|
gateways?: number;
|
|
441
468
|
routes?: number;
|
|
442
|
-
nodes: { total: number; ready: number; notReady: number; cordoned: number };
|
|
469
|
+
nodes: { total: number; ready: number; notReady: number; cordoned: number; removing?: number; removingUnhealthy?: number };
|
|
443
470
|
namespaces: number;
|
|
444
471
|
jobs: { total: number; active: number; succeeded: number; failed: number };
|
|
445
472
|
cronJobs: { total: number; active: number; suspended: number };
|
|
@@ -514,6 +541,10 @@ export type { AuditFinding, ResourceGroup, CheckMeta, Check };
|
|
|
514
541
|
|
|
515
542
|
export interface AuditResponse {
|
|
516
543
|
missingInputs?: string[];
|
|
544
|
+
// Absent on a Radar that predates the per-resource opt-out.
|
|
545
|
+
ignoreChecksAnnotation?: string;
|
|
546
|
+
// checkID → namespace → resources whose findings the annotation hid.
|
|
547
|
+
annotationIgnoredByNamespace?: Record<string, Record<string, number>>;
|
|
517
548
|
summary: DashboardAudit;
|
|
518
549
|
findings: AuditFinding[];
|
|
519
550
|
groups: ResourceGroup[];
|
|
@@ -728,9 +759,13 @@ export interface IssuesResponse {
|
|
|
728
759
|
// state 'degraded' means core workload reads are denied, so an empty list may
|
|
729
760
|
// mean "can't see" rather than "nothing broken" — the UI must say so.
|
|
730
761
|
visibility?: { state?: string; impact?: string };
|
|
762
|
+
// Issue sources Radar couldn't check, so their issues may be missing: an
|
|
763
|
+
// empty list is not an all-clear. Today only "helm"; a newer Radar may send
|
|
764
|
+
// others. Absent when every source was checked, and from older Radars.
|
|
765
|
+
unchecked_sources?: { source: string; reason: string }[];
|
|
731
766
|
}
|
|
732
767
|
|
|
733
|
-
export function useIssues(namespaces: string[] = []) {
|
|
768
|
+
export function useIssues(namespaces: string[] = [], enabled = true) {
|
|
734
769
|
const params =
|
|
735
770
|
namespaces.length > 0 ? `?namespaces=${namespaces.join(",")}` : "";
|
|
736
771
|
return useQuery<IssuesResponse>({
|
|
@@ -738,6 +773,7 @@ export function useIssues(namespaces: string[] = []) {
|
|
|
738
773
|
queryFn: () => fetchJSON(`/issues${params}`),
|
|
739
774
|
staleTime: 30000,
|
|
740
775
|
refetchInterval: ISSUES_REFRESH_INTERVAL_MS,
|
|
776
|
+
enabled,
|
|
741
777
|
});
|
|
742
778
|
}
|
|
743
779
|
|
|
@@ -789,6 +825,38 @@ export function useResourceIssues(
|
|
|
789
825
|
});
|
|
790
826
|
}
|
|
791
827
|
|
|
828
|
+
// The Issues page narrowed to one subject: the same per-resource lookup with
|
|
829
|
+
// ?coverage=1, which also says whether Radar reads the subject's kind and what
|
|
830
|
+
// the caller's RBAC withheld. Polls like the page list.
|
|
831
|
+
export interface SubjectIssuesResponse {
|
|
832
|
+
issues: Issue[];
|
|
833
|
+
coverage: "ok" | "syncing" | "notWatched" | "unknownKind";
|
|
834
|
+
withheld?: { issues: number };
|
|
835
|
+
visibility?: { state?: string; impact?: string };
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export function useSubjectIssues(
|
|
839
|
+
subject: { kind: string; group: string; namespace: string; name: string } | null,
|
|
840
|
+
enabled: boolean,
|
|
841
|
+
) {
|
|
842
|
+
const capabilities = useCapabilities();
|
|
843
|
+
const { support, gatedKey, guard } = useRadarFeature('resourceIssueCoverage');
|
|
844
|
+
const pathNs = subject?.namespace ? encodeURIComponent(subject.namespace) : "_";
|
|
845
|
+
const params = new URLSearchParams({ coverage: "1" });
|
|
846
|
+
if (subject?.group) params.set("group", subject.group);
|
|
847
|
+
return useQuery<SubjectIssuesResponse>({
|
|
848
|
+
queryKey: ["issues", "subject", subject?.kind ?? "", subject?.group ?? "", subject?.namespace ?? "", subject?.name ?? "", support, ...gatedKey],
|
|
849
|
+
queryFn: () => {
|
|
850
|
+
if (!capabilities.data && capabilities.error) throw capabilities.error;
|
|
851
|
+
return guard(() => fetchJSON<SubjectIssuesResponse>(`/issues/resource/${encodeURIComponent(subject!.kind)}/${pathNs}/${encodeURIComponent(subject!.name)}?${params}`));
|
|
852
|
+
},
|
|
853
|
+
staleTime: 30000,
|
|
854
|
+
refetchInterval: ISSUES_REFRESH_INTERVAL_MS,
|
|
855
|
+
// The existing route alone cannot prove support for this opt-in shape.
|
|
856
|
+
enabled: enabled && !!subject && !capabilities.isPending,
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
792
860
|
import type { Trace as NetworkTrace, InClusterCapability, DrainPlan, DrainPlanPod } from '@skyhook-io/k8s-ui'
|
|
793
861
|
|
|
794
862
|
// useTrace polls the static path-shaped diagnosis for one network entry
|
|
@@ -1780,13 +1848,14 @@ export function useRadarFeature(feature: RadarFeature) {
|
|
|
1780
1848
|
const currentVersion = versionInfo?.currentVersion || radarVersion
|
|
1781
1849
|
const latestVersion = versionInfo?.latestVersion
|
|
1782
1850
|
const support = radarFeatureSupport(feature, capabilities, currentVersion)
|
|
1851
|
+
// Stable across renders, so a callback built on guard keeps its identity.
|
|
1852
|
+
const guard = useCallback(
|
|
1853
|
+
<T,>(request: () => Promise<T>): Promise<T> => guardRadarFeature(feature, support, { currentVersion, latestVersion }, request),
|
|
1854
|
+
[feature, support, currentVersion, latestVersion],
|
|
1855
|
+
)
|
|
1856
|
+
const gatedKey = useMemo(() => (support === 'unsupported' ? ['radar-feature-unsupported'] : []), [support])
|
|
1783
1857
|
|
|
1784
|
-
return {
|
|
1785
|
-
support,
|
|
1786
|
-
gatedKey: support === 'unsupported' ? ['radar-feature-unsupported'] : [],
|
|
1787
|
-
guard: <T,>(request: () => Promise<T>): Promise<T> =>
|
|
1788
|
-
guardRadarFeature(feature, support, { currentVersion, latestVersion }, request),
|
|
1789
|
-
}
|
|
1858
|
+
return { support, gatedKey, guard }
|
|
1790
1859
|
}
|
|
1791
1860
|
|
|
1792
1861
|
// ============================================================================
|
|
@@ -2447,6 +2516,11 @@ export function useApplicationHistory(
|
|
|
2447
2516
|
});
|
|
2448
2517
|
}
|
|
2449
2518
|
|
|
2519
|
+
// How often a page asks again while Radar is still connecting to argocd-server,
|
|
2520
|
+
// and while it retries a connect that failed.
|
|
2521
|
+
const ARGO_CONNECTING_POLL_MS = 2000;
|
|
2522
|
+
const ARGO_RETRYING_POLL_MS = 10_000;
|
|
2523
|
+
|
|
2450
2524
|
export function useGitOpsTree(
|
|
2451
2525
|
kind: string,
|
|
2452
2526
|
namespace: string,
|
|
@@ -2469,6 +2543,14 @@ export function useGitOpsTree(
|
|
|
2469
2543
|
),
|
|
2470
2544
|
enabled: Boolean(kind && name) && (options?.enabled ?? true),
|
|
2471
2545
|
staleTime: 5000,
|
|
2546
|
+
// Radar answers at once while it connects to argocd-server; ask again
|
|
2547
|
+
// until Argo's per-resource health is in.
|
|
2548
|
+
refetchInterval: (query) =>
|
|
2549
|
+
query.state.data?.healthApiPending
|
|
2550
|
+
? ARGO_CONNECTING_POLL_MS
|
|
2551
|
+
: query.state.data?.healthApiRetrying
|
|
2552
|
+
? ARGO_RETRYING_POLL_MS
|
|
2553
|
+
: false,
|
|
2472
2554
|
});
|
|
2473
2555
|
}
|
|
2474
2556
|
|
|
@@ -2501,13 +2583,38 @@ export function useGitOpsInsights(
|
|
|
2501
2583
|
staleTime: 5000,
|
|
2502
2584
|
refetchInterval: (query) => {
|
|
2503
2585
|
const phase = query.state.data?.summary?.operationPhase;
|
|
2504
|
-
|
|
2505
|
-
|
|
2586
|
+
if (phase === "Running" || phase === "Terminating") {
|
|
2587
|
+
return INSIGHTS_RUNNING_POLL_MS;
|
|
2588
|
+
}
|
|
2589
|
+
const summary = query.state.data?.summary;
|
|
2590
|
+
if (summary?.resourceHealthApiPending || query.state.data?.capabilities?.argoConnecting) {
|
|
2591
|
+
return ARGO_CONNECTING_POLL_MS;
|
|
2592
|
+
}
|
|
2593
|
+
return summary?.resourceHealthApiRetrying || query.state.data?.capabilities?.argoRetrying
|
|
2594
|
+
? ARGO_RETRYING_POLL_MS
|
|
2506
2595
|
: false;
|
|
2507
2596
|
},
|
|
2508
2597
|
});
|
|
2509
2598
|
}
|
|
2510
2599
|
|
|
2600
|
+
// Field-level evidence behind the GitOps write guard: whether each path is in
|
|
2601
|
+
// the object's last client-side apply or owned by its GitOps controller, plus
|
|
2602
|
+
// the owner's sync policy. Read server-side because Radar's caches strip both.
|
|
2603
|
+
export function fetchGitOpsWriteEvidence(body: {
|
|
2604
|
+
kind: string;
|
|
2605
|
+
group: string;
|
|
2606
|
+
namespace: string;
|
|
2607
|
+
name: string;
|
|
2608
|
+
paths: string[];
|
|
2609
|
+
owner?: { kind: string; group: string; namespace: string; name: string };
|
|
2610
|
+
}): Promise<GitOpsWriteEvidence> {
|
|
2611
|
+
return fetchJSON<GitOpsWriteEvidence>("/gitops/write-evidence", {
|
|
2612
|
+
method: "POST",
|
|
2613
|
+
headers: { "Content-Type": "application/json" },
|
|
2614
|
+
body: JSON.stringify(body),
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2511
2618
|
// Full Git-rendered desired-vs-live diff for one Argo CD managed resource.
|
|
2512
2619
|
// ns/name identify the Application; the ref identifies the managed resource.
|
|
2513
2620
|
// Fetched on demand — the caller mounts this only when the user opens "Full
|
|
@@ -2597,18 +2704,55 @@ export function fetchResourceWithRelationships<T>(
|
|
|
2597
2704
|
);
|
|
2598
2705
|
}
|
|
2599
2706
|
|
|
2707
|
+
/**
|
|
2708
|
+
* How often an open detail view re-checks a resource the server said is gone.
|
|
2709
|
+
* Watch events stop refetching it (see refetchOnResourceEvents), so this is
|
|
2710
|
+
* what finds one recreated under the same name when no event names it, such
|
|
2711
|
+
* as a ConfigMap: the server streams adds only for pods.
|
|
2712
|
+
*/
|
|
2713
|
+
const GONE_RECHECK_MS = 60_000;
|
|
2714
|
+
|
|
2715
|
+
export function goneRecheckInterval(
|
|
2716
|
+
query: { state: { error: unknown } },
|
|
2717
|
+
otherwise: number | false | undefined,
|
|
2718
|
+
): number | false | undefined {
|
|
2719
|
+
return isNotFoundError(query.state.error) ? GONE_RECHECK_MS : otherwise;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
export function resourceEventKey(kind: string, namespace: string, name: string): string {
|
|
2723
|
+
return `${kind}/${namespace}/${name}`;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
/**
|
|
2727
|
+
* Whether a batch of watch events for `kind` refetches this ['resource', kind,
|
|
2728
|
+
* namespace, name, group] query. One the server answered 404 for is refetched
|
|
2729
|
+
* only when an event in the batch names its object; otherwise every change to
|
|
2730
|
+
* the kind would ask again for something that is gone.
|
|
2731
|
+
*/
|
|
2732
|
+
export function refetchOnResourceEvents(
|
|
2733
|
+
query: { queryKey: readonly unknown[]; state: { error: unknown } },
|
|
2734
|
+
kind: string,
|
|
2735
|
+
namedObjects: ReadonlySet<string>,
|
|
2736
|
+
): boolean {
|
|
2737
|
+
if (!isNotFoundError(query.state.error)) return true;
|
|
2738
|
+
const [, , namespace, name] = query.queryKey;
|
|
2739
|
+
return namedObjects.has(resourceEventKey(kind, String(namespace ?? ""), String(name ?? "")));
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
// `cacheScope` keeps a caller's copy apart from the shared one, for hosts
|
|
2743
|
+
// that point one QueryClient at several clusters (see useGitOpsWriteGuard).
|
|
2600
2744
|
export function useResource<T>(
|
|
2601
2745
|
kind: string,
|
|
2602
2746
|
namespace: string,
|
|
2603
2747
|
name: string,
|
|
2604
2748
|
group?: string,
|
|
2605
|
-
options?: { enabled?: boolean; refetchInterval?: number | false },
|
|
2749
|
+
options?: { enabled?: boolean; refetchInterval?: number | false; cacheScope?: string },
|
|
2606
2750
|
) {
|
|
2607
2751
|
const query = useQuery<ResourceWithRelationships<T>>({
|
|
2608
|
-
queryKey: ["resource", kind, namespace, name, group],
|
|
2752
|
+
queryKey: ["resource", kind, namespace, name, group, ...(options?.cacheScope ? [options.cacheScope] : [])],
|
|
2609
2753
|
queryFn: () => fetchResourceWithRelationships<T>(kind, namespace, name, group),
|
|
2610
2754
|
enabled: (options?.enabled ?? true) && Boolean(kind && name), // namespace can be empty for cluster-scoped resources
|
|
2611
|
-
refetchInterval: options?.refetchInterval,
|
|
2755
|
+
refetchInterval: (query) => goneRecheckInterval(query, options?.refetchInterval),
|
|
2612
2756
|
// Kind still completing its initial sync: stay in loading and poll until
|
|
2613
2757
|
// it becomes readable instead of erroring out (deep links during startup).
|
|
2614
2758
|
retry: (failureCount, error) => {
|
|
@@ -2636,11 +2780,13 @@ export function useResourceWithRelationships<T>(
|
|
|
2636
2780
|
namespace: string,
|
|
2637
2781
|
name: string,
|
|
2638
2782
|
group?: string,
|
|
2783
|
+
options?: { cacheScope?: string },
|
|
2639
2784
|
) {
|
|
2640
2785
|
return useQuery<ResourceWithRelationships<T>>({
|
|
2641
|
-
queryKey: ["resource", kind, namespace, name, group],
|
|
2786
|
+
queryKey: ["resource", kind, namespace, name, group, ...(options?.cacheScope ? [options.cacheScope] : [])],
|
|
2642
2787
|
queryFn: () => fetchResourceWithRelationships<T>(kind, namespace, name, group),
|
|
2643
2788
|
enabled: Boolean(kind && name),
|
|
2789
|
+
refetchInterval: (query) => goneRecheckInterval(query, undefined),
|
|
2644
2790
|
// Deep-linked detail views can mount while the kind's informer is still
|
|
2645
2791
|
// completing its initial sync: keep polling instead of erroring out.
|
|
2646
2792
|
retry: (failureCount, error) => {
|
|
@@ -2658,7 +2804,7 @@ export function useResources<T>(
|
|
|
2658
2804
|
kind: string,
|
|
2659
2805
|
namespace?: string,
|
|
2660
2806
|
group?: string,
|
|
2661
|
-
options?: { enabled?: boolean; refetchInterval?: number | false },
|
|
2807
|
+
options?: { enabled?: boolean; refetchInterval?: number | false; cacheScope?: string },
|
|
2662
2808
|
) {
|
|
2663
2809
|
const params = new URLSearchParams();
|
|
2664
2810
|
if (namespace) params.set("namespace", namespace);
|
|
@@ -2666,7 +2812,7 @@ export function useResources<T>(
|
|
|
2666
2812
|
const queryString = params.toString();
|
|
2667
2813
|
|
|
2668
2814
|
return useQuery<T[]>({
|
|
2669
|
-
queryKey: ["resources", kind, group, namespace],
|
|
2815
|
+
queryKey: ["resources", kind, group, namespace, ...(options?.cacheScope ? [options.cacheScope] : [])],
|
|
2670
2816
|
queryFn: () =>
|
|
2671
2817
|
fetchJSON(`/resources/${kind}${queryString ? `?${queryString}` : ""}`),
|
|
2672
2818
|
enabled: (options?.enabled ?? true) && Boolean(kind),
|
|
@@ -2981,6 +3127,38 @@ export function useWorkloadHistory(kind: string, namespace: string, name: string
|
|
|
2981
3127
|
return query;
|
|
2982
3128
|
}
|
|
2983
3129
|
|
|
3130
|
+
export interface ServiceEndpointSlicesResult {
|
|
3131
|
+
items: any[];
|
|
3132
|
+
/** More slices are labeled for the Service than the server returns in one page. */
|
|
3133
|
+
truncated: boolean;
|
|
3134
|
+
}
|
|
3135
|
+
|
|
3136
|
+
// The EndpointSlices published for one Service, selected server-side by the
|
|
3137
|
+
// kubernetes.io/service-name label. A Radar that predates the endpoint settles
|
|
3138
|
+
// as unsupported; ServiceRenderer then falls back to the namespace list.
|
|
3139
|
+
export function useServiceEndpointSlices(namespace: string, name: string, enabled = true) {
|
|
3140
|
+
const { guard, gatedKey, support } = useRadarFeature("serviceEndpointSlices");
|
|
3141
|
+
const query = useQuery<ServiceEndpointSlicesResult>({
|
|
3142
|
+
queryKey: ["service-endpointslices", namespace, name, ...gatedKey],
|
|
3143
|
+
queryFn: ({ signal }) =>
|
|
3144
|
+
guard(() =>
|
|
3145
|
+
fetchJSON<ServiceEndpointSlicesResult>(
|
|
3146
|
+
`/services/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/endpointslices`,
|
|
3147
|
+
signal,
|
|
3148
|
+
),
|
|
3149
|
+
),
|
|
3150
|
+
retry: shouldRetryRadarQuery,
|
|
3151
|
+
refetchInterval: (query) =>
|
|
3152
|
+
query.state.error instanceof RadarFeatureUnsupportedError ? false : 30000,
|
|
3153
|
+
enabled,
|
|
3154
|
+
});
|
|
3155
|
+
const { error, refetch } = query;
|
|
3156
|
+
useEffect(() => {
|
|
3157
|
+
if (support === "supported" && error instanceof RadarFeatureUnsupportedError) void refetch();
|
|
3158
|
+
}, [support, error, refetch]);
|
|
3159
|
+
return query;
|
|
3160
|
+
}
|
|
3161
|
+
|
|
2984
3162
|
export function fetchWorkloadHistoryPage(kind: string, namespace: string, name: string, group: string | undefined, beforeSeq: number): Promise<WorkloadHistoryPage> {
|
|
2985
3163
|
return fetchJSON(workloadHistoryPath(kind, namespace, name, group, beforeSeq));
|
|
2986
3164
|
}
|
|
@@ -3114,23 +3292,31 @@ function retryMetricsQuery(failureCount: number, error: unknown): boolean {
|
|
|
3114
3292
|
return !isMetricsUnavailableError(error) && failureCount < 1;
|
|
3115
3293
|
}
|
|
3116
3294
|
|
|
3295
|
+
// The query behind usePodMetrics, for callers reading several Pods at once
|
|
3296
|
+
// (useQueries) that must share its cache and its null-when-unavailable rule.
|
|
3297
|
+
// null means no metrics for this Pod: the metrics API is missing, or the Pod
|
|
3298
|
+
// has not been scraped yet; one Pod's null cannot tell those apart.
|
|
3299
|
+
export function podMetricsQuery(namespace: string, podName: string, enabled = true) {
|
|
3300
|
+
return {
|
|
3301
|
+
queryKey: ["pod-metrics", namespace, podName] as const,
|
|
3302
|
+
queryFn: () =>
|
|
3303
|
+
fetchMetricsOrNull<PodMetrics>(`/metrics/pods/${namespace}/${podName}`),
|
|
3304
|
+
enabled: Boolean(namespace && podName) && enabled,
|
|
3305
|
+
staleTime: 15000,
|
|
3306
|
+
refetchInterval: 30000,
|
|
3307
|
+
refetchOnMount: "always" as const,
|
|
3308
|
+
refetchOnReconnect: "always" as const,
|
|
3309
|
+
retry: retryMetricsQuery,
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3117
3313
|
// Fetch metrics for a specific pod
|
|
3118
3314
|
export function usePodMetrics(
|
|
3119
3315
|
namespace: string,
|
|
3120
3316
|
podName: string,
|
|
3121
3317
|
options?: { enabled?: boolean },
|
|
3122
3318
|
) {
|
|
3123
|
-
return useQuery<PodMetrics | null>(
|
|
3124
|
-
queryKey: ["pod-metrics", namespace, podName],
|
|
3125
|
-
queryFn: () =>
|
|
3126
|
-
fetchMetricsOrNull<PodMetrics>(`/metrics/pods/${namespace}/${podName}`),
|
|
3127
|
-
enabled: Boolean(namespace && podName) && (options?.enabled ?? true),
|
|
3128
|
-
staleTime: 15000,
|
|
3129
|
-
refetchInterval: 30000,
|
|
3130
|
-
refetchOnMount: "always",
|
|
3131
|
-
refetchOnReconnect: "always",
|
|
3132
|
-
retry: retryMetricsQuery,
|
|
3133
|
-
});
|
|
3319
|
+
return useQuery<PodMetrics | null>(podMetricsQuery(namespace, podName, options?.enabled ?? true));
|
|
3134
3320
|
}
|
|
3135
3321
|
|
|
3136
3322
|
export function usePodEnvironment(namespace: string, podName: string, enabled = true) {
|
|
@@ -3197,6 +3383,7 @@ export interface ContainerMetricsHistory {
|
|
|
3197
3383
|
}
|
|
3198
3384
|
|
|
3199
3385
|
export interface PodMetricsHistory {
|
|
3386
|
+
metricsAPIReachable?: boolean;
|
|
3200
3387
|
namespace: string;
|
|
3201
3388
|
name: string;
|
|
3202
3389
|
containers: ContainerMetricsHistory[];
|
|
@@ -3432,7 +3619,7 @@ export type PrometheusTimeRange =
|
|
|
3432
3619
|
// PVC usage at a moment in time, derived from kubelet_volume_stats_*.
|
|
3433
3620
|
export interface PrometheusPVCUsage {
|
|
3434
3621
|
// Hub packages the frontend independently from per-cluster agent upgrades.
|
|
3435
|
-
status?: "available" | "no_series" | "invalid_data" | "query_failed";
|
|
3622
|
+
status?: "available" | "no_series" | "invalid_data" | "query_failed" | "ambiguous_scope" | "scope_mismatch";
|
|
3436
3623
|
namespace: string;
|
|
3437
3624
|
name: string;
|
|
3438
3625
|
used: number;
|
|
@@ -3580,6 +3767,8 @@ export interface ArgoStatus {
|
|
|
3580
3767
|
// client is live. The two differ right after a restart (configured, reconnecting).
|
|
3581
3768
|
configured: boolean;
|
|
3582
3769
|
connected: boolean;
|
|
3770
|
+
// Not connected yet, and a connection is being made.
|
|
3771
|
+
connecting?: boolean;
|
|
3583
3772
|
// Connected without a token: the install serves reads to everyone, so
|
|
3584
3773
|
// there is nothing to add in Settings.
|
|
3585
3774
|
anonymous?: boolean;
|
|
@@ -3592,8 +3781,12 @@ export function useArgoStatus(enabled = true) {
|
|
|
3592
3781
|
queryKey: ["argocd-status"],
|
|
3593
3782
|
queryFn: () => fetchJSON("/integrations/argocd/status"),
|
|
3594
3783
|
enabled,
|
|
3595
|
-
|
|
3596
|
-
|
|
3784
|
+
// Cheap to ask (it never waits on Argo CD): reopening Settings asks
|
|
3785
|
+
// afresh, so "Connecting…" shows only while it's true.
|
|
3786
|
+
staleTime: 0,
|
|
3787
|
+
// While Radar connects, ask again soon so "Connecting…" doesn't linger.
|
|
3788
|
+
refetchInterval: (query) =>
|
|
3789
|
+
query.state.data?.connecting ? ARGO_CONNECTING_POLL_MS : 60000,
|
|
3597
3790
|
});
|
|
3598
3791
|
}
|
|
3599
3792
|
|
|
@@ -4102,7 +4295,7 @@ export function useUpdateResource() {
|
|
|
4102
4295
|
});
|
|
4103
4296
|
if (!response.ok) {
|
|
4104
4297
|
const error = await readErrorBody(response);
|
|
4105
|
-
const failure = new
|
|
4298
|
+
const failure = new ApiError(error.error || `HTTP ${response.status}`, response.status, error);
|
|
4106
4299
|
// A reviewed apply's failure is shown in the review itself (the
|
|
4107
4300
|
// changed-after-review notice, or the error), so it gets no toast.
|
|
4108
4301
|
if (reviewedResourceVersion !== undefined || reviewedContext !== undefined) {
|
|
@@ -4157,7 +4350,7 @@ export function useUpdateResource() {
|
|
|
4157
4350
|
});
|
|
4158
4351
|
}
|
|
4159
4352
|
|
|
4160
|
-
// Cascade delete preview —
|
|
4353
|
+
// Cascade delete preview — visible related resources deletion may affect.
|
|
4161
4354
|
export interface CascadeDeletePreview {
|
|
4162
4355
|
root: { kind: string; namespace: string; name: string; group?: string };
|
|
4163
4356
|
rootResolved: boolean;
|
|
@@ -4467,6 +4660,7 @@ export interface ApplyResourceResult {
|
|
|
4467
4660
|
namespace: string;
|
|
4468
4661
|
kind: string;
|
|
4469
4662
|
apiVersion: string;
|
|
4663
|
+
uid?: string;
|
|
4470
4664
|
created: boolean;
|
|
4471
4665
|
}
|
|
4472
4666
|
|
|
@@ -5332,6 +5526,23 @@ export function useDrainPlan() {
|
|
|
5332
5526
|
});
|
|
5333
5527
|
}
|
|
5334
5528
|
|
|
5529
|
+
export function useNodeRemovalPlan(name: string | undefined, enabled: boolean) {
|
|
5530
|
+
const { guard, gatedKey } = useRadarFeature('drainPlan')
|
|
5531
|
+
return useQuery<DrainPlan>({
|
|
5532
|
+
queryKey: ['resources', 'node-removal-plan', name, ...gatedKey],
|
|
5533
|
+
queryFn: ({ signal }) => guard(() => fetchJSON<DrainPlan>(drainPlanPath(name!), {
|
|
5534
|
+
method: 'POST',
|
|
5535
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5536
|
+
body: drainPlanBody({ force: true, deleteEmptyDirData: true }),
|
|
5537
|
+
signal,
|
|
5538
|
+
})),
|
|
5539
|
+
enabled: enabled && Boolean(name),
|
|
5540
|
+
staleTime: ISSUES_REFRESH_INTERVAL_MS,
|
|
5541
|
+
refetchInterval: (query) => query.state.error instanceof RadarFeatureUnsupportedError ? false : ISSUES_REFRESH_INTERVAL_MS,
|
|
5542
|
+
retry: shouldRetryRadarQuery,
|
|
5543
|
+
})
|
|
5544
|
+
}
|
|
5545
|
+
|
|
5335
5546
|
export function useDrainNode() {
|
|
5336
5547
|
const queryClient = useQueryClient();
|
|
5337
5548
|
|
|
@@ -6037,11 +6248,79 @@ export function useHelmOCISources() {
|
|
|
6037
6248
|
});
|
|
6038
6249
|
}
|
|
6039
6250
|
|
|
6251
|
+
export function useHelmSourceStatus(namespace: string, releaseName: string, enabled = true) {
|
|
6252
|
+
return useQuery<ChartSourceStatus>({
|
|
6253
|
+
queryKey: ["helm-source-status", namespace, releaseName],
|
|
6254
|
+
queryFn: () => fetchJSON(`/helm/releases/${encodeURIComponent(namespace)}/${encodeURIComponent(releaseName)}/source`),
|
|
6255
|
+
enabled: enabled && Boolean(namespace && releaseName),
|
|
6256
|
+
});
|
|
6257
|
+
}
|
|
6258
|
+
|
|
6259
|
+
async function mutateHelmSource(namespace: string, releaseName: string, source: ChartSourceCandidate): Promise<void> {
|
|
6260
|
+
const response = await apiFetch(apiUrl(`/helm/releases/${encodeURIComponent(namespace)}/${encodeURIComponent(releaseName)}/source`), {
|
|
6261
|
+
method: "PUT",
|
|
6262
|
+
headers: { "Content-Type": "application/json" },
|
|
6263
|
+
body: JSON.stringify(source),
|
|
6264
|
+
});
|
|
6265
|
+
if (!response.ok) {
|
|
6266
|
+
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
6267
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
6268
|
+
}
|
|
6269
|
+
}
|
|
6270
|
+
|
|
6271
|
+
export function useSetHelmSource(namespace: string, releaseName: string) {
|
|
6272
|
+
const queryClient = useQueryClient();
|
|
6273
|
+
return useMutation({
|
|
6274
|
+
mutationFn: (source: ChartSourceCandidate) => mutateHelmSource(namespace, releaseName, source),
|
|
6275
|
+
meta: { errorMessage: "Failed to associate chart source", successMessage: "Chart source associated" },
|
|
6276
|
+
onSuccess: () => {
|
|
6277
|
+
queryClient.invalidateQueries({ queryKey: ["helm-source-status", namespace, releaseName] });
|
|
6278
|
+
queryClient.invalidateQueries({ queryKey: ["helm-upgrade-info"] });
|
|
6279
|
+
queryClient.invalidateQueries({ queryKey: ["helm-release-versions"] });
|
|
6280
|
+
queryClient.invalidateQueries({ queryKey: ["helm-batch-upgrade-info"] });
|
|
6281
|
+
},
|
|
6282
|
+
});
|
|
6283
|
+
}
|
|
6284
|
+
|
|
6285
|
+
interface AddHelmRepositoryRequest {
|
|
6286
|
+
name: string
|
|
6287
|
+
url: string
|
|
6288
|
+
namespace?: string
|
|
6289
|
+
releaseName?: string
|
|
6290
|
+
}
|
|
6291
|
+
|
|
6292
|
+
export function useAddHelmRepository() {
|
|
6293
|
+
const queryClient = useQueryClient();
|
|
6294
|
+
return useMutation({
|
|
6295
|
+
mutationFn: async (request: AddHelmRepositoryRequest): Promise<{ status: string; name: string }> => {
|
|
6296
|
+
const response = await apiFetch(apiUrl('/helm/repositories'), {
|
|
6297
|
+
method: "POST",
|
|
6298
|
+
headers: { "Content-Type": "application/json" },
|
|
6299
|
+
body: JSON.stringify(request),
|
|
6300
|
+
});
|
|
6301
|
+
if (!response.ok) {
|
|
6302
|
+
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
6303
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
6304
|
+
}
|
|
6305
|
+
return response.json();
|
|
6306
|
+
},
|
|
6307
|
+
meta: { errorMessage: "Failed to add Helm repository", successMessage: "Helm repository added" },
|
|
6308
|
+
// Also on error: the repository can be saved before associating it fails.
|
|
6309
|
+
onSettled: () => {
|
|
6310
|
+
queryClient.invalidateQueries({ queryKey: ["helm-repositories"] });
|
|
6311
|
+
queryClient.invalidateQueries({ queryKey: ["helm-source-status"] });
|
|
6312
|
+
queryClient.invalidateQueries({ queryKey: ["helm-upgrade-info"] });
|
|
6313
|
+
queryClient.invalidateQueries({ queryKey: ["helm-release-versions"] });
|
|
6314
|
+
queryClient.invalidateQueries({ queryKey: ["helm-batch-upgrade-info"] });
|
|
6315
|
+
},
|
|
6316
|
+
});
|
|
6317
|
+
}
|
|
6318
|
+
|
|
6040
6319
|
async function mutateOCISource(
|
|
6041
6320
|
method: "POST" | "DELETE",
|
|
6042
6321
|
source: string,
|
|
6043
6322
|
): Promise<string[]> {
|
|
6044
|
-
const response = await apiFetch(
|
|
6323
|
+
const response = await apiFetch(apiUrl('/helm/oci-sources'), {
|
|
6045
6324
|
method,
|
|
6046
6325
|
headers: { "Content-Type": "application/json" },
|
|
6047
6326
|
body: JSON.stringify({ source }),
|
|
@@ -6053,14 +6332,18 @@ async function mutateOCISource(
|
|
|
6053
6332
|
return response.json();
|
|
6054
6333
|
}
|
|
6055
6334
|
|
|
6056
|
-
// Invalidate
|
|
6057
|
-
// immediately
|
|
6058
|
-
function invalidateHelmAfterSourceChange(
|
|
6335
|
+
// Invalidate source inventory/status and upgrade info so an open source dialog
|
|
6336
|
+
// and upgrade checks immediately reflect source additions or removals.
|
|
6337
|
+
export function invalidateHelmAfterSourceChange(
|
|
6059
6338
|
queryClient: ReturnType<typeof useQueryClient>,
|
|
6060
6339
|
) {
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6340
|
+
return Promise.all([
|
|
6341
|
+
queryClient.invalidateQueries({ queryKey: ["helm-oci-sources"] }),
|
|
6342
|
+
queryClient.invalidateQueries({ queryKey: ["helm-source-status"] }),
|
|
6343
|
+
queryClient.invalidateQueries({ queryKey: ["helm-upgrade-info"] }),
|
|
6344
|
+
queryClient.invalidateQueries({ queryKey: ["helm-release-versions"] }),
|
|
6345
|
+
queryClient.invalidateQueries({ queryKey: ["helm-batch-upgrade-info"] }),
|
|
6346
|
+
])
|
|
6064
6347
|
}
|
|
6065
6348
|
|
|
6066
6349
|
export function useAddOCISource() {
|
|
@@ -6413,13 +6696,16 @@ export const useFluxResume = createGitOpsMutation<FluxResourceVars>({
|
|
|
6413
6696
|
export const useFluxSyncWithSource = createGitOpsMutation<FluxResourceVars>({
|
|
6414
6697
|
getPath: (v) => `/flux/${v.kind}/${v.namespace}/${v.name}/sync-with-source`,
|
|
6415
6698
|
errorMessage: "Failed to sync with source",
|
|
6416
|
-
|
|
6699
|
+
// The server names what it reconciled, and says when a source was skipped.
|
|
6700
|
+
getSuccessMessage: (data) => data.message,
|
|
6417
6701
|
getInvalidateKeys: (v) => [
|
|
6418
6702
|
...fluxInvalidateKeys(v),
|
|
6419
6703
|
// Also invalidate source resources as they were reconciled too
|
|
6420
6704
|
["resources", "gitrepositories"],
|
|
6421
6705
|
["resources", "ocirepositories"],
|
|
6422
6706
|
["resources", "helmrepositories"],
|
|
6707
|
+
["resources", "helmcharts"],
|
|
6708
|
+
["resources", "buckets"],
|
|
6423
6709
|
],
|
|
6424
6710
|
});
|
|
6425
6711
|
|
|
@@ -6675,8 +6961,35 @@ export function useNamespaceScope() {
|
|
|
6675
6961
|
});
|
|
6676
6962
|
}
|
|
6677
6963
|
|
|
6678
|
-
const NAMESPACE_SWITCH_TIMEOUT =
|
|
6964
|
+
const NAMESPACE_SWITCH_TIMEOUT = 15000;
|
|
6679
6965
|
const NAMESPACE_RESCOPE_TIMEOUT = 120000;
|
|
6966
|
+
const NAMESPACE_RECONCILE_TIMEOUT = 5000;
|
|
6967
|
+
|
|
6968
|
+
// After a switch times out on the client the server may still have applied
|
|
6969
|
+
// it. Returns the server's scope when it holds exactly the requested pick, so
|
|
6970
|
+
// a slow success is not reported as a failure.
|
|
6971
|
+
export async function reconcileNamespaceSwitch(
|
|
6972
|
+
namespaces: string[],
|
|
6973
|
+
): Promise<NamespaceScope | null> {
|
|
6974
|
+
const controller = new AbortController();
|
|
6975
|
+
const timeoutId = setTimeout(
|
|
6976
|
+
() => controller.abort(),
|
|
6977
|
+
NAMESPACE_RECONCILE_TIMEOUT,
|
|
6978
|
+
);
|
|
6979
|
+
try {
|
|
6980
|
+
const scope = await fetchJSON<NamespaceScope>(
|
|
6981
|
+
"/cluster/namespace-scope",
|
|
6982
|
+
controller.signal,
|
|
6983
|
+
);
|
|
6984
|
+
const want = [...new Set(namespaces)].sort().join(",");
|
|
6985
|
+
const got = [...new Set(scope.actives)].sort().join(",");
|
|
6986
|
+
return want === got ? scope : null;
|
|
6987
|
+
} catch {
|
|
6988
|
+
return null;
|
|
6989
|
+
} finally {
|
|
6990
|
+
clearTimeout(timeoutId);
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6680
6993
|
|
|
6681
6994
|
export function debugNamespaceLog(
|
|
6682
6995
|
label: string,
|
|
@@ -6692,6 +7005,20 @@ export function debugNamespaceLog(
|
|
|
6692
7005
|
});
|
|
6693
7006
|
}
|
|
6694
7007
|
|
|
7008
|
+
/**
|
|
7009
|
+
* A cache-scoped switch rebuilds the watched inventory. View-filter switches
|
|
7010
|
+
* use namespace-specific query keys and do not invalidate the whole client.
|
|
7011
|
+
*/
|
|
7012
|
+
export async function refreshAfterNamespaceSwitch(
|
|
7013
|
+
queryClient: QueryClient,
|
|
7014
|
+
scope: Pick<NamespaceScope, "cacheScoped" | "actives">,
|
|
7015
|
+
): Promise<void> {
|
|
7016
|
+
if (!scope.cacheScoped) return;
|
|
7017
|
+
const notScope = (query: { queryKey: readonly unknown[] }) =>
|
|
7018
|
+
query.queryKey[0] !== "namespace-scope";
|
|
7019
|
+
await queryClient.invalidateQueries({ predicate: notScope }, { cancelRefetch: false });
|
|
7020
|
+
}
|
|
7021
|
+
|
|
6695
7022
|
export function useSetActiveNamespace() {
|
|
6696
7023
|
const queryClient = useQueryClient();
|
|
6697
7024
|
return useMutation<NamespaceScope, Error, { namespaces: string[] }>({
|
|
@@ -6713,7 +7040,7 @@ export function useSetActiveNamespace() {
|
|
|
6713
7040
|
// flag). If the scope query is missing/stale we can't yet tell a cheap
|
|
6714
7041
|
// view-filter change from a cache-rebuilding rescope, so bias to the long
|
|
6715
7042
|
// timeout — only a confirmed non-scoped session gets the fast switch timeout.
|
|
6716
|
-
// Aborting a real rebuild at
|
|
7043
|
+
// Aborting a real rebuild at the switch timeout surfaces a spurious failure while the server
|
|
6717
7044
|
// keeps going.
|
|
6718
7045
|
const isRescope = currentScope?.cacheScoped !== false;
|
|
6719
7046
|
const timeoutMs = isRescope
|
|
@@ -6747,6 +7074,13 @@ export function useSetActiveNamespace() {
|
|
|
6747
7074
|
error: error instanceof Error ? error.message : String(error),
|
|
6748
7075
|
});
|
|
6749
7076
|
if (error instanceof Error && error.name === "AbortError") {
|
|
7077
|
+
// A rescope publishes its target before the rebuild finishes and can
|
|
7078
|
+
// still roll back, so only a view-filter pick is proven by the scope.
|
|
7079
|
+
const applied = isRescope ? null : await reconcileNamespaceSwitch(namespaces);
|
|
7080
|
+
if (applied) {
|
|
7081
|
+
debugNamespaceLog("mutation:timeout-but-applied", { namespaces });
|
|
7082
|
+
return applied;
|
|
7083
|
+
}
|
|
6750
7084
|
throw new Error(
|
|
6751
7085
|
isRescope
|
|
6752
7086
|
? "Namespace rescope timed out. The cluster may still be loading."
|
|
@@ -6769,9 +7103,7 @@ export function useSetActiveNamespace() {
|
|
|
6769
7103
|
});
|
|
6770
7104
|
}
|
|
6771
7105
|
queryClient.setQueryData<NamespaceScope>(["namespace-scope"], scope);
|
|
6772
|
-
|
|
6773
|
-
queryClient.invalidateQueries();
|
|
6774
|
-
}
|
|
7106
|
+
void refreshAfterNamespaceSwitch(queryClient, scope);
|
|
6775
7107
|
debugNamespaceLog("mutation:success-after-scope-cache-write");
|
|
6776
7108
|
},
|
|
6777
7109
|
onError: () => {
|
|
@@ -7215,6 +7547,8 @@ export interface DiagnosticsSnapshot {
|
|
|
7215
7547
|
};
|
|
7216
7548
|
timeline?: {
|
|
7217
7549
|
storageType: string;
|
|
7550
|
+
degraded?: boolean;
|
|
7551
|
+
degradedReason?: string;
|
|
7218
7552
|
totalEvents: number;
|
|
7219
7553
|
oldestEvent?: string;
|
|
7220
7554
|
newestEvent?: string;
|