@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/README.md
CHANGED
|
@@ -10,7 +10,7 @@ This package is source-only — it ships TypeScript + TSX files under `src/`. Co
|
|
|
10
10
|
npm install @skyhook-io/radar-app
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Peer deps: `react >=19`, `react-dom >=19`, `react-router-dom >=7`, `@tanstack/react-query >=5`, `@skyhook-io/k8s-ui >=1.
|
|
13
|
+
Peer deps: `react >=19`, `react-dom >=19`, `react-router-dom >=7`, `@tanstack/react-query >=5`, `@skyhook-io/k8s-ui >=1.17.1`, plus `clsx`, `tailwind-merge`, `lucide-react`, `@xyflow/react`, `elkjs`.
|
|
14
14
|
|
|
15
15
|
## Use
|
|
16
16
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyhook-io/radar-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
4
4
|
"description": "Radar's full web UI as a reusable React component. Used by Radar's own binary and by external consumers like Radar Cloud.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@fontsource/dm-mono": "^5.3.0",
|
|
35
35
|
"@monaco-editor/react": "^4.7.0",
|
|
36
36
|
"diff": "^9.0.0",
|
|
37
|
-
"monaco-editor": "0.
|
|
37
|
+
"monaco-editor": "0.57.0",
|
|
38
38
|
"react-markdown": "^10.1.0",
|
|
39
39
|
"react-virtuoso": "^4.18.16",
|
|
40
40
|
"remark-gfm": "^4.0.1",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"yaml": "^2.9.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@skyhook-io/k8s-ui": ">=1.
|
|
45
|
+
"@skyhook-io/k8s-ui": ">=1.17.1",
|
|
46
46
|
"@tanstack/react-query": ">=5",
|
|
47
47
|
"@xyflow/react": ">=12.0.0",
|
|
48
48
|
"clsx": ">=2",
|
package/src/App.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
|
|
1
|
+
import { useState, useEffect, useLayoutEffect, useCallback, useMemo, useRef } from 'react'
|
|
2
2
|
import { flushSync } from 'react-dom'
|
|
3
3
|
import { startViewTransitionSafe } from '@skyhook-io/k8s-ui/utils/view-transition'
|
|
4
4
|
import { englishPlural } from '@skyhook-io/k8s-ui/utils/pluralize'
|
|
@@ -10,7 +10,7 @@ import { GlobalDiagnoseButton } from './components/diagnose/LocalDiagnoseAction'
|
|
|
10
10
|
import { investigationWorkspaceSearch, isInvestigationWorkspacePath, useDiagnoseLayout } from './components/diagnose/DiagnoseContext'
|
|
11
11
|
import { DiagnoseSurface } from './components/diagnose/DiagnoseSurface'
|
|
12
12
|
import { TopologyGraph, TopologySearch, TopologyBreadcrumb, TopologyFilterSidebar, TopologyControls, FreshnessControl, gitOpsRouteForKind, gitOpsRouteForResource, ScopePill, PaneLoader } from '@skyhook-io/k8s-ui'
|
|
13
|
-
import {
|
|
13
|
+
import { resetNavigationMap } from '@skyhook-io/k8s-ui/utils/navigation'
|
|
14
14
|
import { topologyNodeResourceKind } from '@skyhook-io/k8s-ui/utils/topology-neighborhood'
|
|
15
15
|
import { useAPIResources, findAPIResourceForRoute } from './api/apiResources'
|
|
16
16
|
import { TimelineView } from './components/timeline/TimelineView'
|
|
@@ -23,7 +23,6 @@ import { HelmView } from './components/helm/HelmView'
|
|
|
23
23
|
import { HelmCompareRoute } from './components/helm/HelmCompareRoute'
|
|
24
24
|
import { TrafficView } from './components/traffic/TrafficView'
|
|
25
25
|
import { CostView } from './components/cost/CostView'
|
|
26
|
-
import { CapacityView } from './components/capacity/CapacityView'
|
|
27
26
|
import { AuditView } from './components/audit/AuditView'
|
|
28
27
|
import { IssuesPane } from './components/issues/IssuesPane'
|
|
29
28
|
import { GitOpsView } from './components/gitops/GitOpsView'
|
|
@@ -38,9 +37,10 @@ import { CloudFunnelButton } from './components/CloudFunnelButton'
|
|
|
38
37
|
import { useNavCustomization } from './context/NavCustomization'
|
|
39
38
|
import type { FleetTakeoverTarget } from './context/NavCustomization'
|
|
40
39
|
import { PrimaryNavRail } from './components/nav/PrimaryNavRail'
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
40
|
+
import { resourceExpandedPath, resourceKindListTitle } from './integrations/resourceHosts'
|
|
41
|
+
import { workspaceContextSwitchSearch, workspaceForPath, workspaceForView, workspaceLabels } from './integrations/workspaceRoutes'
|
|
42
|
+
import { WorkspaceScreen } from './integrations/workspaceScreens'
|
|
43
|
+
import { currentPageLabel } from './utils/page-links'
|
|
44
44
|
import { navigateFromPrimaryRail } from './components/nav/navigation'
|
|
45
45
|
import { useNavRailPinned } from './hooks/useNavRailPinned'
|
|
46
46
|
import { useMediaQuery } from './hooks/useMediaQuery'
|
|
@@ -58,12 +58,12 @@ import { openWhatsNew, useWhatsNewStatus, WhatsNew } from './components/whats-ne
|
|
|
58
58
|
import { useUsageData, useUsageRecording } from './api/usage-data'
|
|
59
59
|
import { UsageDataPrompt } from './components/usage-data/UsageDataPrompt'
|
|
60
60
|
import { ShortcutHelpOverlay } from './components/ui/ShortcutHelpOverlay'
|
|
61
|
-
import { DiagnosticsOverlay } from './components/ui/DiagnosticsOverlay'
|
|
61
|
+
import { DiagnosticsOverlay, type DiagnosticsFocus } from './components/ui/DiagnosticsOverlay'
|
|
62
62
|
import { useEventSource } from './hooks/useEventSource'
|
|
63
|
-
import { debugNamespaceLog, useNamespaces, useNamespaceScope, useSetActiveNamespace, useSwitchContext, useAuthMe, useAudit } from './api/client'
|
|
63
|
+
import { debugNamespaceLog, refetchOnResourceEvents, resourceEventKey, useNamespaces, useNamespaceScope, useSetActiveNamespace, useSwitchContext, useAuthMe, useAudit, useClusterInfo } from './api/client'
|
|
64
64
|
import { buildAuditSeverityMap } from './utils/auditBadges'
|
|
65
65
|
import { isInNamespaceScope, scopeNodesToNamespaces } from './utils/topology-namespace'
|
|
66
|
-
import { routePath, apiUrl, getAuthHeaders, getCredentialsMode, stripBasename } from './api/config'
|
|
66
|
+
import { routePath, apiUrl, getApiBase, getAuthHeaders, getCredentialsMode, stripBasename } from './api/config'
|
|
67
67
|
import { KeyboardShortcutProvider, useRegisterShortcut, useRegisterShortcuts, useSuppressBaseShortcuts } from './hooks/useKeyboardShortcuts'
|
|
68
68
|
import { useAnimatedUnmount } from './hooks/useAnimatedUnmount'
|
|
69
69
|
import { useDocumentTitle } from './hooks/useDocumentTitle'
|
|
@@ -75,7 +75,9 @@ import { Tooltip } from './components/ui/Tooltip'
|
|
|
75
75
|
import { LargeClusterNamespacePicker } from './components/shared/LargeClusterNamespacePicker'
|
|
76
76
|
import { SettingsDialog, type SettingsSectionId } from './components/settings/SettingsDialog'
|
|
77
77
|
import type { APIResource, TopologyNode, GroupingMode, MainView, SelectedResource, SelectedHelmRelease, NodeKind, TopologyMode, Topology, K8sEvent } from './types'
|
|
78
|
-
import {
|
|
78
|
+
import { createViewMemory } from './utils/viewMemory'
|
|
79
|
+
import { createKindFilterStore } from './utils/kindFilterStore'
|
|
80
|
+
import { kindToPluralWithGroup, pluralToKind, openExternal, apiVersionToGroup, relatedResourcePath, searchHitToSelectedResource, withCrossViewParams, writeViewParams, CROSS_VIEW_PARAMS } from './utils/navigation'
|
|
79
81
|
import { findSelectedTopologyNode } from './utils/topology-selection'
|
|
80
82
|
import { type OmnibarHandle } from './components/ui/Omnibar'
|
|
81
83
|
import { RadarOmnibar } from './components/ui/RadarOmnibar'
|
|
@@ -83,7 +85,7 @@ import type { ContextSwitcherHandle } from './components/ContextSwitcher'
|
|
|
83
85
|
|
|
84
86
|
// All possible node kinds (core + GitOps)
|
|
85
87
|
const ALL_NODE_KINDS: NodeKind[] = [
|
|
86
|
-
'Internet', 'Ingress', 'Gateway', 'HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute',
|
|
88
|
+
'Internet', 'Ingress', 'Gateway', 'HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute', 'UDPRoute',
|
|
87
89
|
'Service', 'Deployment', 'Rollout', 'DaemonSet', 'StatefulSet',
|
|
88
90
|
'ReplicaSet', 'Pod', 'PodGroup', 'ConfigMap', 'Secret', 'ServiceAccount', 'SealedSecret', 'ServiceMonitor', 'PodMonitor', 'HorizontalPodAutoscaler', 'Job', 'CronJob', 'PersistentVolumeClaim', 'Namespace',
|
|
89
91
|
'Application', 'Kustomization', 'HelmRelease', 'GitRepository',
|
|
@@ -141,8 +143,8 @@ function getViewFromPath(pathname: string): ExtendedMainView {
|
|
|
141
143
|
if (path === 'helm') return 'helm'
|
|
142
144
|
if (path === 'traffic') return 'traffic'
|
|
143
145
|
if (path === 'cost') return 'cost'
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
+
const workspace = workspaceForPath(pathname)
|
|
147
|
+
if (workspace) return workspace.id
|
|
146
148
|
if (path === 'workload') return 'workload'
|
|
147
149
|
if (path === 'checks' || path === 'audit') return 'checks' // /audit = legacy → checks
|
|
148
150
|
if (path === 'gitops') return 'gitops'
|
|
@@ -167,13 +169,14 @@ function usageView(pathname: string, view: ExtendedMainView, upgrade: boolean):
|
|
|
167
169
|
|
|
168
170
|
// The screen a crash is counted under. Fixed names, because release builds
|
|
169
171
|
// shorten component names and the component stack can't say which screen.
|
|
170
|
-
const CRASH_LABELS
|
|
172
|
+
const CRASH_LABELS = {
|
|
171
173
|
home: 'Home', topology: 'Topology', resources: 'Resources', timeline: 'Timeline',
|
|
172
174
|
issues: 'Issues', helm: 'Helm', helmCompare: 'HelmCompare', traffic: 'Traffic',
|
|
173
|
-
cost: 'Cost',
|
|
175
|
+
cost: 'Cost', checks: 'Checks', gitops: 'GitOps',
|
|
174
176
|
applications: 'Applications', workload: 'Workload', compare: 'Compare',
|
|
175
177
|
investigations: 'Investigations',
|
|
176
|
-
|
|
178
|
+
...workspaceLabels,
|
|
179
|
+
} satisfies Record<ExtendedMainView, string>
|
|
177
180
|
|
|
178
181
|
// The namespace scope filter is meaningful only on namespaced surfaces. On
|
|
179
182
|
// cluster-scoped views it does nothing, so we disable it with an explanation
|
|
@@ -203,12 +206,8 @@ function namespaceFilterDisabled(
|
|
|
203
206
|
tooltip: 'Cost is reported per namespace across the whole cluster — the namespace filter doesn’t apply here.',
|
|
204
207
|
}
|
|
205
208
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
disabled: true,
|
|
209
|
-
tooltip: 'Capacity is reported across the cluster — the namespace filter doesn’t apply here.',
|
|
210
|
-
}
|
|
211
|
-
}
|
|
209
|
+
const workspace = workspaceForView(view)
|
|
210
|
+
if (workspace?.namespaceScope === 'cluster') return { disabled: true, tooltip: workspace.namespaceFilterTooltip }
|
|
212
211
|
if (view === 'checks' && pathname.startsWith('/checks/upgrade')) {
|
|
213
212
|
return {
|
|
214
213
|
disabled: true,
|
|
@@ -263,6 +262,8 @@ function radarPageTitle(pathname: string, search = '', apiResources?: APIResourc
|
|
|
263
262
|
const resourceName = decode(pathSegments[1] ?? '')
|
|
264
263
|
if (!resourceName) return 'Resources'
|
|
265
264
|
const group = new URLSearchParams(search).get('apiGroup') || ''
|
|
265
|
+
const hostTitle = resourceKindListTitle({ name: resourceName, group })
|
|
266
|
+
if (hostTitle) return hostTitle
|
|
266
267
|
const match = findAPIResourceForRoute(apiResources, resourceName, group)
|
|
267
268
|
return pluralKindTitle(match?.kind ?? pluralToKind(resourceName), resourceName)
|
|
268
269
|
}
|
|
@@ -281,18 +282,8 @@ function radarPageTitle(pathname: string, search = '', apiResources?: APIResourc
|
|
|
281
282
|
if (view === 'checks' && pathSegments[1] === 'upgrade') return 'Upgrade impact'
|
|
282
283
|
|
|
283
284
|
// The landing view reads "Overview" rather than "Home" in the tab.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
if (pathSegments[1] === 'demand') return 'Capacity Demand'
|
|
287
|
-
if (pathSegments[1] === 'activity') return 'Capacity Activity'
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (view === 'cnpg') {
|
|
291
|
-
const route = parseCNPGRoute(pathname)
|
|
292
|
-
if (route.detail) return route.detail.name
|
|
293
|
-
const screen = CNPG_SCREENS.find((s) => s.id === route.screen)
|
|
294
|
-
return `CloudNativePG ${screen?.label ?? 'Overview'}`
|
|
295
|
-
}
|
|
285
|
+
const workspace = workspaceForView(view)
|
|
286
|
+
if (workspace) return workspace.pageTitle(pathname)
|
|
296
287
|
if (view === 'home') return 'Overview'
|
|
297
288
|
// Every other view's label is its id capitalized — getViewFromPath has already
|
|
298
289
|
// normalized aliases (e.g. /audit → 'checks'), so no lookup table is needed.
|
|
@@ -351,6 +342,7 @@ interface AppProps {
|
|
|
351
342
|
}
|
|
352
343
|
|
|
353
344
|
function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterLoadStateChange }: AppProps) {
|
|
345
|
+
useLayoutEffect(() => () => resetNavigationMap(), [])
|
|
354
346
|
const navigate = useNavigate()
|
|
355
347
|
const location = useLocation()
|
|
356
348
|
const navigationType = useNavigationType()
|
|
@@ -452,6 +444,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
452
444
|
|
|
453
445
|
// Get mainView from URL path
|
|
454
446
|
const mainView = getViewFromPath(location.pathname)
|
|
447
|
+
const activeWorkspace = workspaceForView(mainView)
|
|
455
448
|
const upgradeReadinessRoute = location.pathname.startsWith('/checks/upgrade')
|
|
456
449
|
|
|
457
450
|
// Opt-in usage data. Embedded hosts own their own consent, so Radar never asks there.
|
|
@@ -459,12 +452,8 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
459
452
|
// A status cached by another screen must not start recording inside a host.
|
|
460
453
|
useUsageRecording(usageView(location.pathname, mainView, upgradeReadinessRoute), navCustomization.embedded ? undefined : usageData.data)
|
|
461
454
|
|
|
462
|
-
//
|
|
463
|
-
// mount) so the omnibar can open a CRD hit with an irregular plural from any
|
|
464
|
-
// view — kindToPlural would otherwise English-guess the route before a
|
|
465
|
-
// resources view has run initNavigationMap().
|
|
455
|
+
// The omnibar needs discovered plurals even before ResourcesView mounts.
|
|
466
456
|
const { data: navApiResources } = useAPIResources()
|
|
467
|
-
useEffect(() => { if (navApiResources) initNavigationMap(navApiResources) }, [navApiResources])
|
|
468
457
|
|
|
469
458
|
// View-aware namespace scope: disabled on cluster-scoped surfaces so the
|
|
470
459
|
// chip isn't a dead control next to the cluster switcher.
|
|
@@ -482,15 +471,45 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
482
471
|
return slug || 'pods'
|
|
483
472
|
}, [location.pathname])
|
|
484
473
|
|
|
485
|
-
//
|
|
474
|
+
// Section memory and Resources filters are kept per cluster; inert until the context is known.
|
|
475
|
+
const { data: clusterInfoData, isLoading: clusterInfoLoading } = useClusterInfo()
|
|
476
|
+
const clusterContext = clusterInfoData?.context
|
|
477
|
+
const viewMemory = useMemo(
|
|
478
|
+
() => (clusterContext ? createViewMemory(`${getApiBase()}|${clusterContext}`) : undefined),
|
|
479
|
+
[clusterContext],
|
|
480
|
+
)
|
|
481
|
+
const kindFilterStore = useMemo(
|
|
482
|
+
() => (clusterContext ? createKindFilterStore(`${getApiBase()}|${clusterContext}`) : undefined),
|
|
483
|
+
[clusterContext],
|
|
484
|
+
)
|
|
485
|
+
useEffect(() => {
|
|
486
|
+
viewMemory?.record(mainView, location.pathname, location.search)
|
|
487
|
+
if (mainView === 'resources') kindFilterStore?.recordLastKind(location.pathname, location.search)
|
|
488
|
+
}, [viewMemory, kindFilterStore, mainView, location.pathname, location.search])
|
|
489
|
+
const rememberedKindSearch = useMemo(
|
|
490
|
+
() => kindFilterStore && ((kind: { name: string; group: string }) => kindFilterStore.get(kind.name, kind.group)),
|
|
491
|
+
[kindFilterStore],
|
|
492
|
+
)
|
|
493
|
+
const saveKindFilters = useMemo(
|
|
494
|
+
() => kindFilterStore && ((kind: { name: string; group: string }, search: string) => kindFilterStore.set(kind.name, kind.group, search)),
|
|
495
|
+
[kindFilterStore],
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
// Canonical URL — `/resources` is not stable for bookmarks/sharing; normalize to
|
|
499
|
+
// the last kind (hosts such as Radar Hub link to bare `/resources`), else Pods.
|
|
486
500
|
useEffect(() => {
|
|
487
501
|
const path = location.pathname.replace(/\/+$/, '') || '/'
|
|
488
|
-
|
|
502
|
+
// Wait for the cluster so its last kind can be read; landing on Pods first would overwrite it.
|
|
503
|
+
if (path !== '/resources' || clusterInfoLoading) return
|
|
504
|
+
const current = new URLSearchParams(location.search)
|
|
505
|
+
const onlyCrossView = [...current.keys()].every(key => (CROSS_VIEW_PARAMS as readonly string[]).includes(key))
|
|
506
|
+
const destination = new URL((onlyCrossView && kindFilterStore?.lastKind()) || '/resources/pods', 'http://radar.invalid')
|
|
507
|
+
current.forEach((value, key) => destination.searchParams.set(key, value))
|
|
489
508
|
navigate(
|
|
490
|
-
{ pathname:
|
|
509
|
+
{ pathname: destination.pathname, search: destination.search, hash: location.hash },
|
|
491
510
|
{ replace: true },
|
|
492
511
|
)
|
|
493
|
-
}, [location.pathname, location.search, location.hash, navigate])
|
|
512
|
+
}, [location.pathname, location.search, location.hash, navigate, kindFilterStore, clusterInfoLoading])
|
|
494
513
|
|
|
495
514
|
// Set mainView by navigating to the path
|
|
496
515
|
const setMainView = useCallback((view: ExtendedMainView, params?: Record<string, string>) => {
|
|
@@ -508,6 +527,15 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
508
527
|
}
|
|
509
528
|
}
|
|
510
529
|
|
|
530
|
+
// Explicit params are a link with its own state; otherwise go back to where the user left this section.
|
|
531
|
+
const remembered = params
|
|
532
|
+
? undefined
|
|
533
|
+
: viewMemory?.sectionPath(view) ?? (view === 'resources' ? kindFilterStore?.lastKind() : undefined)
|
|
534
|
+
if (remembered) {
|
|
535
|
+
navigate(withCrossViewParams(remembered, location.search))
|
|
536
|
+
return
|
|
537
|
+
}
|
|
538
|
+
|
|
511
539
|
const path = view === 'home' ? '/' : `/${view}`
|
|
512
540
|
|
|
513
541
|
// Start fresh — keep only cross-view params, discard view-specific ones.
|
|
@@ -532,7 +560,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
532
560
|
}
|
|
533
561
|
|
|
534
562
|
navigate({ pathname: path, search: newParams.toString() })
|
|
535
|
-
}, [location.search, navigate, takeover, goHost])
|
|
563
|
+
}, [location.search, navigate, takeover, goHost, viewMemory, kindFilterStore])
|
|
536
564
|
|
|
537
565
|
const whatsNewStatus = useWhatsNewStatus()
|
|
538
566
|
|
|
@@ -637,6 +665,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
637
665
|
|
|
638
666
|
// Diagnostics overlay state
|
|
639
667
|
const [showDiagnostics, setShowDiagnostics] = useState(false)
|
|
668
|
+
const [diagnosticsFocus, setDiagnosticsFocus] = useState<DiagnosticsFocus>()
|
|
640
669
|
|
|
641
670
|
// The peek drawer "expanded" into a fullscreen overlay = ?full=1 with a selected
|
|
642
671
|
// resource, on ANY view (resources list, topology graph, GitOps, Applications…) —
|
|
@@ -953,7 +982,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
953
982
|
category: 'General' as const,
|
|
954
983
|
scope: 'global' as const,
|
|
955
984
|
allowInInputs: true,
|
|
956
|
-
handler: () => setShowDiagnostics(prev => !prev),
|
|
985
|
+
handler: () => { setDiagnosticsFocus(undefined); setShowDiagnostics(prev => !prev) },
|
|
957
986
|
},
|
|
958
987
|
// Settings exposes local-binary controls that don't apply to embedded hosts.
|
|
959
988
|
// Register the shortcut only when standalone (matching the gear button) —
|
|
@@ -1069,12 +1098,13 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1069
1098
|
// update that arrived *after* the fast structural flush already ran.
|
|
1070
1099
|
const fastInvalidationRef = useRef<{
|
|
1071
1100
|
changedKinds: Set<string> // every changed kind (any op) → detail drawer
|
|
1101
|
+
changedObjects: Set<string> // resourceEventKey of every event → detail views that got a 404
|
|
1072
1102
|
structuralKinds: Set<string> // add/delete kinds → list membership + counts + dashboard
|
|
1073
1103
|
environmentNamespaces: Set<string>
|
|
1074
1104
|
environmentPods: Map<string, Set<string>>
|
|
1075
1105
|
secretsChanged: boolean
|
|
1076
1106
|
timer: number | null
|
|
1077
|
-
}>({ changedKinds: new Set(), structuralKinds: new Set(), environmentNamespaces: new Set(), environmentPods: new Map(), secretsChanged: false, timer: null })
|
|
1107
|
+
}>({ changedKinds: new Set(), changedObjects: new Set(), structuralKinds: new Set(), environmentNamespaces: new Set(), environmentPods: new Map(), secretsChanged: false, timer: null })
|
|
1078
1108
|
const cnpgInvalidationPendingRef = useRef(false)
|
|
1079
1109
|
const slowInvalidationRef = useRef<{
|
|
1080
1110
|
updatedKinds: Set<string> // update-only churn → throttled list + dashboard
|
|
@@ -1111,6 +1141,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1111
1141
|
|
|
1112
1142
|
const fast = fastInvalidationRef.current
|
|
1113
1143
|
fast.changedKinds.add(kind)
|
|
1144
|
+
fast.changedObjects.add(resourceEventKey(kind, event.namespace ?? '', event.name))
|
|
1114
1145
|
if (structural) fast.structuralKinds.add(kind)
|
|
1115
1146
|
if (kind === 'secrets') fast.secretsChanged = true
|
|
1116
1147
|
if ((kind === 'configmaps' || kind === 'secrets') && event.namespace) fast.environmentNamespaces.add(event.namespace)
|
|
@@ -1128,7 +1159,12 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1128
1159
|
fast.timer = window.setTimeout(() => {
|
|
1129
1160
|
const f = fastInvalidationRef.current
|
|
1130
1161
|
for (const k of f.changedKinds) {
|
|
1131
|
-
|
|
1162
|
+
// Open detail drawers stay live; a deleted one is left alone unless
|
|
1163
|
+
// an event names it.
|
|
1164
|
+
queryClient.invalidateQueries({
|
|
1165
|
+
queryKey: ['resource', k],
|
|
1166
|
+
predicate: (q) => refetchOnResourceEvents(q, k, f.changedObjects),
|
|
1167
|
+
})
|
|
1132
1168
|
}
|
|
1133
1169
|
for (const k of f.structuralKinds) {
|
|
1134
1170
|
queryClient.invalidateQueries({ queryKey: ['resources', k] }) // list membership changed
|
|
@@ -1156,7 +1192,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1156
1192
|
cnpgInvalidationPendingRef.current = false
|
|
1157
1193
|
queryClient.invalidateQueries({ queryKey: ['cnpg', 'workspace'] })
|
|
1158
1194
|
}
|
|
1159
|
-
fastInvalidationRef.current = { changedKinds: new Set(), structuralKinds: new Set(), environmentNamespaces: new Set(), environmentPods: new Map(), secretsChanged: false, timer: null }
|
|
1195
|
+
fastInvalidationRef.current = { changedKinds: new Set(), changedObjects: new Set(), structuralKinds: new Set(), environmentNamespaces: new Set(), environmentPods: new Map(), secretsChanged: false, timer: null }
|
|
1160
1196
|
}, 3000)
|
|
1161
1197
|
}
|
|
1162
1198
|
|
|
@@ -1185,7 +1221,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1185
1221
|
if (fastInvalidationRef.current.timer !== null) clearTimeout(fastInvalidationRef.current.timer)
|
|
1186
1222
|
if (slowInvalidationRef.current.timer !== null) clearTimeout(slowInvalidationRef.current.timer)
|
|
1187
1223
|
if (timelineInvalidationRef.current.timer !== null) clearTimeout(timelineInvalidationRef.current.timer)
|
|
1188
|
-
fastInvalidationRef.current = { changedKinds: new Set(), structuralKinds: new Set(), environmentNamespaces: new Set(), environmentPods: new Map(), secretsChanged: false, timer: null }
|
|
1224
|
+
fastInvalidationRef.current = { changedKinds: new Set(), changedObjects: new Set(), structuralKinds: new Set(), environmentNamespaces: new Set(), environmentPods: new Map(), secretsChanged: false, timer: null }
|
|
1189
1225
|
slowInvalidationRef.current = { updatedKinds: new Set(), timer: null }
|
|
1190
1226
|
timelineInvalidationRef.current = { timer: null }
|
|
1191
1227
|
}, [])
|
|
@@ -1198,6 +1234,8 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1198
1234
|
onContextSwitchComplete: endSwitch,
|
|
1199
1235
|
onContextSwitchProgress: updateProgress,
|
|
1200
1236
|
onContextChanged: (context) => {
|
|
1237
|
+
resetNavigationMap()
|
|
1238
|
+
|
|
1201
1239
|
// Clear all React Query caches when cluster context changes
|
|
1202
1240
|
// This ensures helm releases, resources, etc. are refetched from the new cluster
|
|
1203
1241
|
// removeQueries clears cached data, invalidateQueries triggers refetch
|
|
@@ -1208,7 +1246,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1208
1246
|
if (fastInvalidationRef.current.timer !== null) clearTimeout(fastInvalidationRef.current.timer)
|
|
1209
1247
|
if (slowInvalidationRef.current.timer !== null) clearTimeout(slowInvalidationRef.current.timer)
|
|
1210
1248
|
if (timelineInvalidationRef.current.timer !== null) clearTimeout(timelineInvalidationRef.current.timer)
|
|
1211
|
-
fastInvalidationRef.current = { changedKinds: new Set(), structuralKinds: new Set(), environmentNamespaces: new Set(), environmentPods: new Map(), secretsChanged: false, timer: null }
|
|
1249
|
+
fastInvalidationRef.current = { changedKinds: new Set(), changedObjects: new Set(), structuralKinds: new Set(), environmentNamespaces: new Set(), environmentPods: new Map(), secretsChanged: false, timer: null }
|
|
1212
1250
|
slowInvalidationRef.current = { updatedKinds: new Set(), timer: null }
|
|
1213
1251
|
timelineInvalidationRef.current = { timer: null }
|
|
1214
1252
|
|
|
@@ -1241,10 +1279,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1241
1279
|
const nextParams = new URLSearchParams()
|
|
1242
1280
|
const diagnoseRun = new URLSearchParams(location.search).get('ai-run')
|
|
1243
1281
|
if (diagnoseRun) nextParams.set('ai-run', diagnoseRun)
|
|
1244
|
-
|
|
1245
|
-
// not in the new one instead of loading a same-named object.
|
|
1246
|
-
const pinnedCtx = new URLSearchParams(location.search).get('ctx')
|
|
1247
|
-
if (pinnedCtx && location.pathname.startsWith('/cnpg/')) nextParams.set('ctx', pinnedCtx)
|
|
1282
|
+
for (const [key, value] of workspaceContextSwitchSearch(location.pathname, new URLSearchParams(location.search))) nextParams.set(key, value)
|
|
1248
1283
|
navigate(
|
|
1249
1284
|
{ pathname: location.pathname, search: nextParams.toString() },
|
|
1250
1285
|
{ replace: true, state: location.state },
|
|
@@ -1425,6 +1460,34 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1425
1460
|
{ onSuccess: () => setNamespaces([]) },
|
|
1426
1461
|
)
|
|
1427
1462
|
}, [namespaceScope?.cacheScoped, namespaces.length, setActiveNamespace])
|
|
1463
|
+
// Same write as the header picker, which then mirrors it into `namespaces`.
|
|
1464
|
+
// The flash shows where that scope lives from now on. The picker owns the
|
|
1465
|
+
// namespace scope from then on, so no kind keeps a Namespace column filter.
|
|
1466
|
+
const [namespacePickerFlash, setNamespacePickerFlash] = useState(0)
|
|
1467
|
+
const applyNamespacesToAllSections = useCallback(
|
|
1468
|
+
(picked: string[]) => setActiveNamespace.mutateAsync({ namespaces: picked }).then(() => {
|
|
1469
|
+
kindFilterStore?.clearColumn('namespace')
|
|
1470
|
+
setNamespacePickerFlash(n => n + 1)
|
|
1471
|
+
}),
|
|
1472
|
+
[setActiveNamespace, kindFilterStore],
|
|
1473
|
+
)
|
|
1474
|
+
const issuesShowNamespace = useMemo(() => {
|
|
1475
|
+
if (namespaceScope?.cacheScoped) {
|
|
1476
|
+
if (!namespaceScope.namespaceRescope) return undefined
|
|
1477
|
+
return {
|
|
1478
|
+
mode: 'switch' as const,
|
|
1479
|
+
show: (ns: string) => { setNamespaces([ns]); setActiveNamespace.mutate({ namespaces: [ns] }) },
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
return {
|
|
1483
|
+
mode: 'add' as const,
|
|
1484
|
+
show: (ns: string) => {
|
|
1485
|
+
const next = namespaces.includes(ns) ? namespaces : [...namespaces, ns]
|
|
1486
|
+
setNamespaces(next)
|
|
1487
|
+
setActiveNamespace.mutate({ namespaces: next })
|
|
1488
|
+
},
|
|
1489
|
+
}
|
|
1490
|
+
}, [namespaceScope?.cacheScoped, namespaceScope?.namespaceRescope, namespaces, setActiveNamespace])
|
|
1428
1491
|
const initialBookmarkReconciledRef = useRef(false)
|
|
1429
1492
|
const scopeActives = useMemo(() => namespaceScope?.actives ?? [], [namespaceScope?.actives])
|
|
1430
1493
|
const namespaceScopeKey = useMemo(() => namespaceScope ? [...scopeActives].sort().join(',') : null, [namespaceScope, scopeActives])
|
|
@@ -1503,32 +1566,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1503
1566
|
if (skipUrlWriteAfterPopRef.current) return
|
|
1504
1567
|
const currentSearch = window.location.search
|
|
1505
1568
|
const params = new URLSearchParams(currentSearch)
|
|
1506
|
-
|
|
1507
|
-
// Update namespaces param
|
|
1508
|
-
if (namespaces.length > 0) {
|
|
1509
|
-
params.set('namespaces', namespaces.join(','))
|
|
1510
|
-
} else {
|
|
1511
|
-
params.delete('namespaces')
|
|
1512
|
-
}
|
|
1513
|
-
// Remove legacy 'namespace' param if present
|
|
1514
|
-
params.delete('namespace')
|
|
1515
|
-
|
|
1516
|
-
// Topology-specific params: only set when on topology view, clean up otherwise
|
|
1517
|
-
if (mainView === 'topology') {
|
|
1518
|
-
if (topologyMode !== 'resources') {
|
|
1519
|
-
params.set('mode', topologyMode)
|
|
1520
|
-
} else {
|
|
1521
|
-
params.delete('mode')
|
|
1522
|
-
}
|
|
1523
|
-
if (groupingMode !== 'none' && (namespaces.length === 0 || groupingMode !== 'namespace')) {
|
|
1524
|
-
params.set('group', groupingMode)
|
|
1525
|
-
} else {
|
|
1526
|
-
params.delete('group')
|
|
1527
|
-
}
|
|
1528
|
-
} else {
|
|
1529
|
-
params.delete('mode')
|
|
1530
|
-
params.delete('group')
|
|
1531
|
-
}
|
|
1569
|
+
writeViewParams(params, namespaces, mainView === 'topology' ? { mode: topologyMode, grouping: groupingMode } : null)
|
|
1532
1570
|
|
|
1533
1571
|
// Only update if params actually changed vs current URL
|
|
1534
1572
|
if (params.toString() !== new URLSearchParams(currentSearch).toString()) {
|
|
@@ -1771,7 +1809,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1771
1809
|
setVisibleKinds(new Set())
|
|
1772
1810
|
}, [])
|
|
1773
1811
|
|
|
1774
|
-
const navActiveView = mainView === 'helmCompare' ? 'helm' :
|
|
1812
|
+
const navActiveView = mainView === 'helmCompare' ? 'helm' : activeWorkspace?.navView ?? mainView
|
|
1775
1813
|
|
|
1776
1814
|
return (
|
|
1777
1815
|
<PortForwardProvider>
|
|
@@ -1814,7 +1852,9 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1814
1852
|
<ScopePill>
|
|
1815
1853
|
<ContextSwitcher ref={contextSwitcherRef} variant="segment" />
|
|
1816
1854
|
<NamespaceSwitcher
|
|
1855
|
+
key={namespacePickerFlash}
|
|
1817
1856
|
ref={namespaceSwitcherRef}
|
|
1857
|
+
className={namespacePickerFlash ? 'reach-flash' : undefined}
|
|
1818
1858
|
variant="segment"
|
|
1819
1859
|
disabled={namespaceFilter.disabled}
|
|
1820
1860
|
disabledTooltip={namespaceFilter.tooltip}
|
|
@@ -2230,7 +2270,8 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2230
2270
|
)}
|
|
2231
2271
|
|
|
2232
2272
|
{/* Resources view */}
|
|
2233
|
-
{
|
|
2273
|
+
{/* Bare /resources waits for App's redirect to the last kind; mounted, the view would redirect to Pods first. */}
|
|
2274
|
+
{mainView === 'resources' && (location.pathname.replace(/\/+$/, '') || '/') !== '/resources' && (
|
|
2234
2275
|
<ResourcesView
|
|
2235
2276
|
namespaces={namespaces}
|
|
2236
2277
|
selectedResource={routeSelectedResource}
|
|
@@ -2238,6 +2279,11 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2238
2279
|
onResourceClickYaml={(res) => navigateToResource(res, 'yaml')}
|
|
2239
2280
|
onKindChange={() => setSelectedResource(null)}
|
|
2240
2281
|
onClearNamespaces={clearAllNamespaces}
|
|
2282
|
+
rememberedKindSearch={rememberedKindSearch}
|
|
2283
|
+
onKindFiltersChange={saveKindFilters}
|
|
2284
|
+
// Embedded hosts own their namespace picker, and a cache-scoped
|
|
2285
|
+
// Radar can't take an arbitrary set.
|
|
2286
|
+
onApplyNamespacesToAllSections={namespaceScope?.cacheScoped || !kindFilterStore ? undefined : applyNamespacesToAllSections}
|
|
2241
2287
|
/>
|
|
2242
2288
|
)}
|
|
2243
2289
|
|
|
@@ -2257,6 +2303,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2257
2303
|
setNamespaces([ns])
|
|
2258
2304
|
setActiveNamespace.mutate({ namespaces: [ns] })
|
|
2259
2305
|
}}
|
|
2306
|
+
onOpenDiagnostics={() => { setDiagnosticsFocus('timeline'); setShowDiagnostics(true) }}
|
|
2260
2307
|
/>
|
|
2261
2308
|
)}
|
|
2262
2309
|
|
|
@@ -2313,7 +2360,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2313
2360
|
|
|
2314
2361
|
{/* Traffic view */}
|
|
2315
2362
|
{!viewsSyncGated && mainView === 'traffic' && (
|
|
2316
|
-
<TrafficView namespaces={namespaces} />
|
|
2363
|
+
<TrafficView namespaces={namespaces} onSetNamespaces={(ns) => { setNamespaces(ns); setActiveNamespace.mutate({ namespaces: ns }) }} />
|
|
2317
2364
|
)}
|
|
2318
2365
|
|
|
2319
2366
|
{/* Cost detail view */}
|
|
@@ -2321,12 +2368,9 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2321
2368
|
<CostView namespaces={namespaces} onBack={() => setMainView('home')} onOpenResource={navigateToResource} />
|
|
2322
2369
|
)}
|
|
2323
2370
|
|
|
2324
|
-
{!viewsSyncGated &&
|
|
2325
|
-
<
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
{!viewsSyncGated && mainView === 'cnpg' && (
|
|
2329
|
-
<CNPGView
|
|
2371
|
+
{!viewsSyncGated && activeWorkspace && (
|
|
2372
|
+
<WorkspaceScreen
|
|
2373
|
+
workspace={activeWorkspace}
|
|
2330
2374
|
namespaces={namespaces}
|
|
2331
2375
|
selectedResource={routeSelectedResource}
|
|
2332
2376
|
onOpenResource={navigateToResource}
|
|
@@ -2365,6 +2409,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2365
2409
|
<IssuesPane
|
|
2366
2410
|
namespaces={namespaces}
|
|
2367
2411
|
onNavigateToResource={navigateFromIssue}
|
|
2412
|
+
showNamespace={issuesShowNamespace}
|
|
2368
2413
|
/>
|
|
2369
2414
|
)}
|
|
2370
2415
|
|
|
@@ -2403,10 +2448,10 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2403
2448
|
onNavigate={(res) => navigateToResource(res)}
|
|
2404
2449
|
canCollapseToDrawer={!isMobile}
|
|
2405
2450
|
onExpand={(res, opts) => {
|
|
2406
|
-
const
|
|
2407
|
-
if (
|
|
2451
|
+
const detailPath = resourceExpandedPath(res, connection.context || undefined, opts?.yaml ? 'yaml' : undefined)
|
|
2452
|
+
if (detailPath) {
|
|
2408
2453
|
navigate(
|
|
2409
|
-
|
|
2454
|
+
detailPath,
|
|
2410
2455
|
{ state: { returnLabel: currentPageLabel(), returnCtx: connection.context } },
|
|
2411
2456
|
)
|
|
2412
2457
|
return
|
|
@@ -2534,7 +2579,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2534
2579
|
{helpOverlay.shouldRender && <ShortcutHelpOverlay isOpen={helpOverlay.isOpen} onClose={() => setShowHelp(false)} currentView={mainView} />}
|
|
2535
2580
|
|
|
2536
2581
|
{/* Diagnostics overlay */}
|
|
2537
|
-
{diagnosticsOverlay.shouldRender && <DiagnosticsOverlay isOpen={diagnosticsOverlay.isOpen} onClose={() => setShowDiagnostics(false)} />}
|
|
2582
|
+
{diagnosticsOverlay.shouldRender && <DiagnosticsOverlay isOpen={diagnosticsOverlay.isOpen} focus={diagnosticsFocus} onClose={() => { setShowDiagnostics(false); setDiagnosticsFocus(undefined) }} />}
|
|
2538
2583
|
|
|
2539
2584
|
{/* Settings dialog — My permissions is rendered inline in its own section */}
|
|
2540
2585
|
<SettingsDialog
|
package/src/RadarApp.tsx
CHANGED
|
@@ -46,6 +46,7 @@ import type {
|
|
|
46
46
|
import type { ClusterLoadState } from "./types/clusterLoadState";
|
|
47
47
|
import { TimelineSourceProvider } from "./context/TimelineSource";
|
|
48
48
|
import type { TimelineSourceConfig } from "./api/timelineSource";
|
|
49
|
+
import { TimelineRetentionNoteProvider } from "./context/TimelineCustomization";
|
|
49
50
|
import { DiagnoseCustomizationProvider } from "./context/DiagnoseCustomization";
|
|
50
51
|
import type {
|
|
51
52
|
RenderDiagnoseAction,
|
|
@@ -180,6 +181,12 @@ export interface RadarAppProps {
|
|
|
180
181
|
* React hook-order violation). Set it once at mount when possible.
|
|
181
182
|
*/
|
|
182
183
|
timelineSource?: TimelineSourceConfig;
|
|
184
|
+
/**
|
|
185
|
+
* A note shown above the timeline and the Applications history, saying how
|
|
186
|
+
* far back history goes. Omitted → no note. Set it only when the timeline server
|
|
187
|
+
* actually limits history, or the note misstates what is shown.
|
|
188
|
+
*/
|
|
189
|
+
timelineRetentionNote?: React.ReactNode;
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
// Default QueryClient with the same shape Radar's standalone binary uses.
|
|
@@ -236,6 +243,7 @@ export function RadarApp({
|
|
|
236
243
|
onClusterLoadStateChange,
|
|
237
244
|
onInvestigationFocus,
|
|
238
245
|
timelineSource,
|
|
246
|
+
timelineRetentionNote,
|
|
239
247
|
}: RadarAppProps): React.ReactElement {
|
|
240
248
|
// Apply runtime config during render so module-level singletons are set
|
|
241
249
|
// before children construct URLs. getApiBase() / getAuthHeaders() /
|
|
@@ -266,24 +274,26 @@ export function RadarApp({
|
|
|
266
274
|
<RadarUpgradeHostProvider value={upgradeHost}>
|
|
267
275
|
<FilterLocationBridge>
|
|
268
276
|
<TimelineSourceProvider config={timelineSource}>
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
<DiagnoseProvider
|
|
276
|
-
browserURLState={router !== "memory"}
|
|
277
|
-
forceRouterURLState={router === "memory"}
|
|
278
|
-
onFocusedRun={onInvestigationFocus}
|
|
277
|
+
<TimelineRetentionNoteProvider value={timelineRetentionNote}>
|
|
278
|
+
<DiagnoseCustomizationProvider
|
|
279
|
+
value={renderDiagnoseAction ?? defaultDiagnoseAction}
|
|
280
|
+
consentCopy={diagnoseConsent}
|
|
281
|
+
renderRunActions={renderInvestigationRunActions}
|
|
282
|
+
renderRefusalAction={renderInvestigationRefusalAction}
|
|
279
283
|
>
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
284
|
+
<DiagnoseProvider
|
|
285
|
+
browserURLState={router !== "memory"}
|
|
286
|
+
forceRouterURLState={router === "memory"}
|
|
287
|
+
onFocusedRun={onInvestigationFocus}
|
|
288
|
+
>
|
|
289
|
+
<App
|
|
290
|
+
manageDocumentTitle={manageDocumentTitle}
|
|
291
|
+
documentTitleSuffix={documentTitleSuffix}
|
|
292
|
+
onClusterLoadStateChange={onClusterLoadStateChange}
|
|
293
|
+
/>
|
|
294
|
+
</DiagnoseProvider>
|
|
295
|
+
</DiagnoseCustomizationProvider>
|
|
296
|
+
</TimelineRetentionNoteProvider>
|
|
287
297
|
</TimelineSourceProvider>
|
|
288
298
|
</FilterLocationBridge>
|
|
289
299
|
</RadarUpgradeHostProvider>
|