@skyhook-io/radar-app 1.13.4 → 1.14.0
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/package.json +8 -8
- package/src/App.tsx +60 -24
- package/src/RadarApp.tsx +5 -0
- package/src/api/client.authRedirect.test.ts +12 -1
- package/src/api/client.prometheusStatus.test.ts +24 -0
- package/src/api/client.ts +325 -16
- package/src/api/diagnose.ts +62 -15
- package/src/api/drain-plan.test.ts +86 -0
- package/src/api/quotas.test.ts +26 -0
- package/src/api/quotas.ts +20 -0
- package/src/api/workloadMetrics.test.ts +40 -0
- package/src/api/workloadMetrics.ts +68 -0
- package/src/components/CloudConnectFlow.tsx +179 -42
- package/src/components/CloudFunnelButton.tsx +107 -36
- package/src/components/ConnectionErrorView.test.tsx +30 -0
- package/src/components/ConnectionErrorView.tsx +31 -19
- package/src/components/SyncProgressPanel.tsx +73 -0
- package/src/components/audit/AuditView.tsx +2 -0
- package/src/components/cloudConnectHandoff.test.ts +140 -0
- package/src/components/cloudConnectHandoff.ts +124 -0
- package/src/components/diagnose/AgentCase.tsx +131 -0
- package/src/components/diagnose/DiagnoseContext.tsx +2 -1
- package/src/components/diagnose/DiagnoseSurface.test.tsx +0 -20
- package/src/components/diagnose/DiagnoseSurface.tsx +32 -195
- package/src/components/diagnose/InvestigationEvidencePane.test.tsx +783 -7
- package/src/components/diagnose/InvestigationEvidencePane.tsx +774 -133
- package/src/components/diagnose/InvestigationView.tsx +222 -5
- package/src/components/diagnose/diagnoseEvidenceTypes.ts +22 -1
- package/src/components/diagnose/investigationCase.test.tsx +1568 -0
- package/src/components/diagnose/investigationCase.ts +439 -0
- package/src/components/diagnose/investigationEvidence/adapters/changes.ts +60 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +573 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +935 -0
- package/src/components/diagnose/investigationEvidence/adapters/events.ts +96 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.test.ts +242 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.ts +208 -0
- package/src/components/diagnose/investigationEvidence/adapters/issues.ts +172 -0
- package/src/components/diagnose/investigationEvidence/adapters/logs.test.ts +283 -0
- package/src/components/diagnose/investigationEvidence/adapters/logs.ts +183 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.test.ts +528 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +292 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.test.ts +1641 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +769 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +54 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.ts +257 -0
- package/src/components/diagnose/investigationEvidence/adapters/topology.ts +207 -0
- package/src/components/diagnose/investigationEvidence/builder.ts +418 -0
- package/src/components/diagnose/investigationEvidence/evidenceFixtures.ts +179 -0
- package/src/components/diagnose/investigationEvidence/identity.test.ts +614 -0
- package/src/components/diagnose/investigationEvidence/identity.ts +261 -0
- package/src/components/diagnose/investigationEvidence/index.ts +55 -0
- package/src/components/diagnose/investigationEvidence/observations.ts +954 -0
- package/src/components/diagnose/investigationEvidence/parse.ts +467 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +2703 -0
- package/src/components/diagnose/investigationEvidence/types.ts +512 -0
- package/src/components/diagnose/investigationEvidenceKinds.ts +218 -0
- package/src/components/diagnose/investigationEvidencePresentation.test.ts +31 -0
- package/src/components/diagnose/investigationEvidencePresentation.ts +1 -0
- package/src/components/diagnose/investigationMetrics.test.ts +712 -0
- package/src/components/diagnose/investigationMetrics.ts +393 -0
- package/src/components/diagnose/investigationSourceFocus.ts +2 -0
- package/src/components/diagnose/investigationState.test.ts +322 -0
- package/src/components/diagnose/investigationState.ts +147 -25
- package/src/components/diagnose/parts.test.tsx +482 -0
- package/src/components/diagnose/parts.tsx +418 -41
- package/src/components/diagnose/target.test.ts +19 -0
- package/src/components/diagnose/target.ts +3 -2
- package/src/components/gitops/GitOpsView.tsx +7 -0
- package/src/components/gitops/RemoteDestinationCloudHint.tsx +26 -0
- package/src/components/resource/PrometheusCharts.overlays.test.ts +22 -0
- package/src/components/resource/PrometheusCharts.tsx +4 -3
- package/src/components/resource/PrometheusChartsGrid.render.test.tsx +99 -0
- package/src/components/resource/PrometheusChartsGrid.tsx +245 -103
- package/src/components/resource/RightsizingStrip.test.ts +38 -0
- package/src/components/resource/RightsizingStrip.tsx +15 -3
- package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +79 -0
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +145 -0
- package/src/components/resource/WorkloadMetricsSection.render.test.tsx +351 -0
- package/src/components/resource/WorkloadMetricsSection.tsx +486 -0
- package/src/components/resource/workloadMetricValues.test.ts +65 -0
- package/src/components/resource/workloadMetricValues.ts +30 -0
- package/src/components/resources/PodFilePreview.test.tsx +131 -0
- package/src/components/resources/PodFilePreview.tsx +394 -0
- package/src/components/resources/PodFilesystemModal.tsx +157 -67
- package/src/components/resources/ResourcesView.tsx +44 -6
- package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -1
- package/src/components/resources/renderers/PodRenderer.tsx +4 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +15 -1
- package/src/components/resources/renderers/WorkloadRenderer.tsx +3 -0
- package/src/components/rightsizing/RightsizingScanView.tsx +21 -0
- package/src/components/settings/SettingsDialog.tsx +94 -14
- package/src/components/traffic/TrafficFlowList.tsx +229 -70
- package/src/components/traffic/TrafficGraph.tsx +6 -6
- package/src/components/traffic/TrafficView.tsx +10 -9
- package/src/components/traffic/trafficFilters.test.ts +28 -1
- package/src/components/traffic/trafficFilters.ts +31 -0
- package/src/components/workload/WorkloadView.tsx +19 -8
- package/src/context/ConnectionContext.tsx +48 -3
- package/src/context/DiagnoseCustomization.test.tsx +26 -0
- package/src/context/DiagnoseCustomization.tsx +15 -2
- package/src/index.css +17 -0
- package/src/index.ts +1 -0
- package/src/utils/navigation.ts +1 -1
- package/src/utils/shell-safe.test.ts +25 -1
- package/src/utils/shell-safe.ts +16 -0
- package/src/utils/topology-namespace.test.ts +64 -0
- package/src/utils/topology-namespace.ts +18 -0
- package/src/components/diagnose/investigationEvidence.test.ts +0 -5202
- package/src/components/diagnose/investigationEvidence.ts +0 -4784
- package/src/components/gitops/GitOpsActions.tsx +0 -1
- package/src/components/gitops/GitOpsStatusBadge.tsx +0 -1
- package/src/components/gitops/ManagedResourcesList.tsx +0 -1
- package/src/components/gitops/SyncCountdown.tsx +0 -1
- package/src/components/gitops/index.ts +0 -4
- package/src/components/helm/ManifestDiffViewer.tsx +0 -69
- package/src/components/logs/JsonLogLine.tsx +0 -1
- package/src/components/logs/LogCore.tsx +0 -2
- package/src/components/logs/useLogBuffer.ts +0 -2
- package/src/components/logs/useLogSearch.ts +0 -1
- package/src/components/resource-drawer/ResourceDrawer.tsx +0 -216
- package/src/components/resources/drawer-components.tsx +0 -1
- package/src/components/resources/renderers/AlertRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +0 -1
- package/src/components/resources/renderers/CertificateRenderer.tsx +0 -1
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +0 -1
- package/src/components/resources/renderers/ChallengeRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +0 -1
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +0 -1
- package/src/components/resources/renderers/CronJobRenderer.tsx +0 -1
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +0 -1
- package/src/components/resources/renderers/EventRenderer.tsx +0 -1
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +0 -1
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/GatewayRenderer.tsx +0 -1
- package/src/components/resources/renderers/GenericRenderer.tsx +0 -1
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/IngressClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/IngressRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +0 -1
- package/src/components/resources/renderers/JobRenderer.tsx +0 -1
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +0 -1
- package/src/components/resources/renderers/KustomizationRenderer.tsx +0 -1
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/LeaseRenderer.tsx +0 -1
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +0 -1
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/OrderRenderer.tsx +0 -1
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +0 -1
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +0 -1
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +0 -1
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +0 -1
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +0 -1
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/SbomReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/SecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +0 -1
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +0 -1
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/StorageClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/VPARenderer.tsx +0 -1
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +0 -1
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +0 -1
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +0 -1
- package/src/components/resources/renderers/WorkflowRenderer.tsx +0 -1
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +0 -1
- package/src/components/resources/renderers/argo-cells.tsx +0 -1
- package/src/components/resources/renderers/certmanager-cells.tsx +0 -1
- package/src/components/resources/renderers/cnpg-cells.tsx +0 -1
- package/src/components/resources/renderers/eso-cells.tsx +0 -1
- package/src/components/resources/renderers/flux-cells.tsx +0 -1
- package/src/components/resources/renderers/index.ts +0 -94
- package/src/components/resources/renderers/istio-cells.tsx +0 -1
- package/src/components/resources/renderers/karpenter-cells.tsx +0 -1
- package/src/components/resources/renderers/keda-cells.tsx +0 -1
- package/src/components/resources/renderers/knative-cells.tsx +0 -1
- package/src/components/resources/renderers/kyverno-cells.tsx +0 -1
- package/src/components/resources/renderers/prometheus-cells.tsx +0 -1
- package/src/components/resources/renderers/traefik-cells.tsx +0 -1
- package/src/components/resources/renderers/trivy-cells.tsx +0 -1
- package/src/components/resources/renderers/trivy-shared.tsx +0 -1
- package/src/components/resources/renderers/velero-cells.tsx +0 -1
- package/src/components/resources/resource-utils-argo.ts +0 -2
- package/src/components/resources/resource-utils-certmanager.ts +0 -2
- package/src/components/resources/resource-utils-eso.ts +0 -2
- package/src/components/resources/resource-utils-flux.ts +0 -2
- package/src/components/resources/resource-utils-istio.ts +0 -2
- package/src/components/resources/resource-utils-karpenter.ts +0 -2
- package/src/components/resources/resource-utils-knative.ts +0 -2
- package/src/components/resources/resource-utils-kyverno.ts +0 -2
- package/src/components/resources/resource-utils-prometheus.ts +0 -2
- package/src/components/resources/resource-utils-traefik.ts +0 -1
- package/src/components/resources/resource-utils-trivy.ts +0 -2
- package/src/components/resources/resource-utils-velero.ts +0 -2
- package/src/components/shared/EditableYamlView.tsx +0 -24
- package/src/components/timeline/DiffViewer.tsx +0 -1
- package/src/components/traffic/index.ts +0 -3
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +0 -1
- package/src/components/ui/MetricsChart.tsx +0 -1
- package/src/components/ui/ResourceBar.tsx +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyhook-io/radar-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
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",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"lint": "eslint src && node scripts/check-no-native-title.mjs"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@fontsource-variable/dm-sans": "^5.
|
|
32
|
+
"@fontsource-variable/dm-sans": "^5.3.0",
|
|
33
33
|
"@fontsource/dm-mono": "^5.3.0",
|
|
34
34
|
"@monaco-editor/react": "^4.7.0",
|
|
35
35
|
"diff": "^9.0.0",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"@tailwindcss/vite": "^4.3.3",
|
|
61
61
|
"@tanstack/react-query": "^5.102.8",
|
|
62
62
|
"@types/node": "^26.2.0",
|
|
63
|
-
"@types/react": "^19.
|
|
64
|
-
"@types/react-dom": "^19.
|
|
65
|
-
"@vitejs/plugin-react": "^6.1.
|
|
63
|
+
"@types/react": "^19.3.0",
|
|
64
|
+
"@types/react-dom": "^19.3.0",
|
|
65
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
66
66
|
"@xyflow/react": "^12.11.2",
|
|
67
67
|
"clsx": "^2.1.1",
|
|
68
68
|
"elkjs": "^0.11.1",
|
|
@@ -73,13 +73,13 @@
|
|
|
73
73
|
"lucide-react": "^1.37.0",
|
|
74
74
|
"postcss": "^8.5.28",
|
|
75
75
|
"prettier": "^3.9.5",
|
|
76
|
-
"react": "^19.
|
|
77
|
-
"react-dom": "^19.
|
|
76
|
+
"react": "^19.3.0",
|
|
77
|
+
"react-dom": "^19.3.0",
|
|
78
78
|
"react-router-dom": "^7.18.3",
|
|
79
79
|
"tailwind-merge": "^3.6.0",
|
|
80
80
|
"tailwindcss": "^4.3.3",
|
|
81
81
|
"typescript": "^6.0.2",
|
|
82
|
-
"typescript-eslint": "^8.
|
|
82
|
+
"typescript-eslint": "^8.70.0",
|
|
83
83
|
"vite": "^8.2.2"
|
|
84
84
|
},
|
|
85
85
|
"sideEffects": [
|
package/src/App.tsx
CHANGED
|
@@ -43,6 +43,7 @@ import { useMediaQuery } from './hooks/useMediaQuery'
|
|
|
43
43
|
import { ContextSwitchProvider, useContextSwitch } from './context/ContextSwitchContext'
|
|
44
44
|
import { ConnectionProvider, useConnection } from './context/ConnectionContext'
|
|
45
45
|
import { ConnectionErrorView } from './components/ConnectionErrorView'
|
|
46
|
+
import { SyncProgressPanel } from './components/SyncProgressPanel'
|
|
46
47
|
import { CapabilitiesProvider, useCapabilitiesContext } from './contexts/CapabilitiesContext'
|
|
47
48
|
import { UserMenu } from './components/UserMenu'
|
|
48
49
|
import { ErrorBoundary } from './components/ui/ErrorBoundary'
|
|
@@ -53,6 +54,7 @@ import { DiagnosticsOverlay } from './components/ui/DiagnosticsOverlay'
|
|
|
53
54
|
import { useEventSource } from './hooks/useEventSource'
|
|
54
55
|
import { debugNamespaceLog, useNamespaces, useNamespaceScope, useSetActiveNamespace, useSwitchContext, useAuthMe, useAudit } from './api/client'
|
|
55
56
|
import { buildAuditSeverityMap } from './utils/auditBadges'
|
|
57
|
+
import { isInNamespaceScope, scopeNodesToNamespaces } from './utils/topology-namespace'
|
|
56
58
|
import { routePath, apiUrl, getAuthHeaders, getCredentialsMode, stripBasename } from './api/config'
|
|
57
59
|
import { KeyboardShortcutProvider, useRegisterShortcut, useRegisterShortcuts, useSuppressBaseShortcuts } from './hooks/useKeyboardShortcuts'
|
|
58
60
|
import { useAnimatedUnmount } from './hooks/useAnimatedUnmount'
|
|
@@ -992,6 +994,15 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
992
994
|
const contentReady = !isSwitching && !authMePending &&
|
|
993
995
|
!(authMe?.authEnabled && !authMe?.username) && connection.state === 'connected'
|
|
994
996
|
|
|
997
|
+
// Progressive shell during the initial informer sync: once the server
|
|
998
|
+
// publishes per-kind sync progress, render the app instead of the splash.
|
|
999
|
+
// Resource views serve kinds as they become ready; everything else shows
|
|
1000
|
+
// the sync progress panel until 'connected'.
|
|
1001
|
+
const shellDuringSync = !isSwitching && !authMePending &&
|
|
1002
|
+
!(authMe?.authEnabled && !authMe?.username) &&
|
|
1003
|
+
connection.state === 'connecting' && !!connection.syncStatus?.kinds?.length
|
|
1004
|
+
const viewsSyncGated = shellDuringSync && mainView !== 'resources'
|
|
1005
|
+
|
|
995
1006
|
const { clusterLoadState, showHomeClusterLoadFallback, clusterLoadInitial } = useClusterLoadState({
|
|
996
1007
|
namespaces,
|
|
997
1008
|
mainView,
|
|
@@ -1245,16 +1256,22 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1245
1256
|
const clusterConnectionState = connection.state
|
|
1246
1257
|
const clusterConnected = clusterConnectionState === 'connected'
|
|
1247
1258
|
const liveUpdatesDisconnected = clusterConnected && !eventStreamConnected && !eventStreamConnecting
|
|
1259
|
+
// During the progressive shell, the header label carries the global sync
|
|
1260
|
+
// progress — the one place that explains why some views are open and
|
|
1261
|
+
// others still loading.
|
|
1262
|
+
const syncProgressLabel = connection.syncStatus
|
|
1263
|
+
? `Loading cluster data — ${connection.syncStatus.criticalSynced + connection.syncStatus.deferredSynced} of ${connection.syncStatus.criticalTotal + connection.syncStatus.deferredTotal} ready`
|
|
1264
|
+
: 'Connecting'
|
|
1248
1265
|
const headerConnectionLabel =
|
|
1249
1266
|
clusterConnectionState === 'disconnected' ? 'Disconnected' :
|
|
1250
|
-
clusterConnectionState === 'connecting' ?
|
|
1267
|
+
clusterConnectionState === 'connecting' ? syncProgressLabel :
|
|
1251
1268
|
liveUpdatesDisconnected ? 'Live updates disconnected' :
|
|
1252
1269
|
clusterLoadState.loading ? `Connected — ${clusterLoadState.message}` :
|
|
1253
1270
|
crdDiscoveryStatus === 'discovering' ? 'Connected — discovering Custom Resources...' :
|
|
1254
1271
|
'Connected'
|
|
1255
1272
|
const headerConnectionDisplayLabel =
|
|
1256
1273
|
clusterConnectionState === 'disconnected' ? 'Disconnected' :
|
|
1257
|
-
clusterConnectionState === 'connecting' ?
|
|
1274
|
+
clusterConnectionState === 'connecting' ? syncProgressLabel :
|
|
1258
1275
|
liveUpdatesDisconnected ? 'Live updates disconnected' :
|
|
1259
1276
|
showClusterWarmupLabel ? clusterLoadState.message :
|
|
1260
1277
|
crdDiscoveryStatus === 'discovering' ? 'Discovering Custom Resources…' :
|
|
@@ -1604,8 +1621,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1604
1621
|
// Filter by namespace (client-side) and by visible kinds
|
|
1605
1622
|
const nsSet = namespaces.length > 0 ? new Set(namespaces) : null
|
|
1606
1623
|
const filteredNodes = displayedTopology.nodes.filter(node =>
|
|
1607
|
-
effectiveKinds.has(node.kind) &&
|
|
1608
|
-
(!nsSet || nsSet.has(node.data.namespace as string) || !(node.data.namespace as string))
|
|
1624
|
+
effectiveKinds.has(node.kind) && isInNamespaceScope(node, nsSet)
|
|
1609
1625
|
)
|
|
1610
1626
|
const filteredNodeIds = new Set(filteredNodes.map(n => n.id))
|
|
1611
1627
|
|
|
@@ -1630,6 +1646,16 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
1630
1646
|
}
|
|
1631
1647
|
}, [displayedTopology, visibleKinds, namespaces, topologyMode])
|
|
1632
1648
|
|
|
1649
|
+
// Namespace-scoped but NOT kind-filtered: the sidebar derives its kind list
|
|
1650
|
+
// and visible/hidden footer counts from this prop, so handing it the
|
|
1651
|
+
// kind-filtered graph nodes would drop every hidden kind from the list and
|
|
1652
|
+
// "hidden" would always read zero. Reads displayedTopology so the counts
|
|
1653
|
+
// freeze with the graph while paused.
|
|
1654
|
+
const filterSidebarNodes = useMemo(() => {
|
|
1655
|
+
if (!displayedTopology) return []
|
|
1656
|
+
return scopeNodesToNamespaces(displayedTopology.nodes, namespaces)
|
|
1657
|
+
}, [displayedTopology, namespaces])
|
|
1658
|
+
|
|
1633
1659
|
// Cluster Audit findings, joined onto topology nodes by the audit key the
|
|
1634
1660
|
// backend stamps on each node (data.auditKey). Only badge-worthy findings
|
|
1635
1661
|
// reach the graph; the raw auditDanger/auditWarning property names remain at
|
|
@@ -2021,7 +2047,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2021
2047
|
{/* Connecting view — shown during initial connection or retry.
|
|
2022
2048
|
Icon is pane-anchored so its screen position matches the
|
|
2023
2049
|
host hub splash across cross-document transitions. */}
|
|
2024
|
-
{!isSwitching && !(authMe?.authEnabled && !authMe?.username) && connection.state === 'connecting' && (
|
|
2050
|
+
{!isSwitching && !(authMe?.authEnabled && !authMe?.username) && connection.state === 'connecting' && !shellDuringSync && (
|
|
2025
2051
|
<PaneLoader
|
|
2026
2052
|
label="Connecting to cluster"
|
|
2027
2053
|
className="flex-1 min-h-0 bg-theme-base"
|
|
@@ -2082,13 +2108,21 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2082
2108
|
{/* inert while a fullscreen detail overlay covers the views — keeps the
|
|
2083
2109
|
retained background list out of the focus order + a11y tree (the visual
|
|
2084
2110
|
cover already blocks pointer events). */}
|
|
2085
|
-
{contentReady && <div className="flex-1 flex overflow-hidden" inert={expandedView}>
|
|
2111
|
+
{(contentReady || shellDuringSync) && <div className="flex-1 flex overflow-hidden" inert={expandedView}>
|
|
2086
2112
|
{/* Search included, not just the path: selection inside a view rides in
|
|
2087
2113
|
the query (?resource=, ?release=), so a path-only key would still
|
|
2088
2114
|
strand a crash on the view that produced it. */}
|
|
2089
2115
|
<ErrorBoundary resetKey={location.pathname + location.search}>
|
|
2116
|
+
{/* Initial sync in progress: views that need the full cluster dataset
|
|
2117
|
+
show per-kind progress instead; resource views work as kinds sync. */}
|
|
2118
|
+
{viewsSyncGated && connection.syncStatus && (
|
|
2119
|
+
<SyncProgressPanel
|
|
2120
|
+
syncStatus={connection.syncStatus}
|
|
2121
|
+
onNavigateToKind={(key) => navigate({ pathname: `/resources/${key}` })}
|
|
2122
|
+
/>
|
|
2123
|
+
)}
|
|
2090
2124
|
{/* Home dashboard */}
|
|
2091
|
-
{mainView === 'home' && (
|
|
2125
|
+
{!viewsSyncGated && mainView === 'home' && (
|
|
2092
2126
|
<HomeView
|
|
2093
2127
|
namespaces={namespaces}
|
|
2094
2128
|
topology={topology}
|
|
@@ -2147,7 +2181,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2147
2181
|
)}
|
|
2148
2182
|
|
|
2149
2183
|
{/* Topology view */}
|
|
2150
|
-
{mainView === 'topology' && (
|
|
2184
|
+
{!viewsSyncGated && mainView === 'topology' && (
|
|
2151
2185
|
<>
|
|
2152
2186
|
{topology?.requiresNamespaceFilter && namespaces.length === 0 ? (
|
|
2153
2187
|
/* Large cluster: prompt user to select a namespace */
|
|
@@ -2182,14 +2216,14 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2182
2216
|
<>
|
|
2183
2217
|
{/* Filter sidebar */}
|
|
2184
2218
|
<TopologyFilterSidebar
|
|
2185
|
-
nodes={
|
|
2219
|
+
nodes={filterSidebarNodes}
|
|
2186
2220
|
visibleKinds={visibleKinds}
|
|
2187
2221
|
onToggleKind={handleToggleKind}
|
|
2188
2222
|
onShowAll={handleShowAllKinds}
|
|
2189
2223
|
onHideAll={handleHideAllKinds}
|
|
2190
2224
|
collapsed={filterSidebarCollapsed}
|
|
2191
2225
|
onToggleCollapse={() => setFilterSidebarCollapsed(prev => !prev)}
|
|
2192
|
-
hiddenKinds={
|
|
2226
|
+
hiddenKinds={displayedTopology?.hiddenKinds}
|
|
2193
2227
|
onEnableHiddenKind={(kind) => {
|
|
2194
2228
|
setVisibleKinds(prev => new Set(prev).add(kind as NodeKind))
|
|
2195
2229
|
console.log(`[topology] User requested to show hidden kind: ${kind}`)
|
|
@@ -2275,7 +2309,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2275
2309
|
)}
|
|
2276
2310
|
|
|
2277
2311
|
{/* Timeline view */}
|
|
2278
|
-
{mainView === 'timeline' && (
|
|
2312
|
+
{!viewsSyncGated && mainView === 'timeline' && (
|
|
2279
2313
|
<TimelineView
|
|
2280
2314
|
namespaces={namespaces}
|
|
2281
2315
|
onResourceClick={(resource) => {
|
|
@@ -2293,7 +2327,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2293
2327
|
/>
|
|
2294
2328
|
)}
|
|
2295
2329
|
|
|
2296
|
-
{mainView === 'helm' && (
|
|
2330
|
+
{!viewsSyncGated && mainView === 'helm' && (
|
|
2297
2331
|
<HelmView
|
|
2298
2332
|
namespaces={namespaces}
|
|
2299
2333
|
selectedRelease={selectedHelmRelease}
|
|
@@ -2301,13 +2335,13 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2301
2335
|
/>
|
|
2302
2336
|
)}
|
|
2303
2337
|
|
|
2304
|
-
{mainView === 'helmCompare' && (
|
|
2338
|
+
{!viewsSyncGated && mainView === 'helmCompare' && (
|
|
2305
2339
|
<HelmCompareRoute />
|
|
2306
2340
|
)}
|
|
2307
2341
|
|
|
2308
2342
|
{/* GitOps view (inline only when the host hasn't taken it over — see
|
|
2309
2343
|
the takeover splash below). */}
|
|
2310
|
-
{mainView === 'gitops' && !isViewTakenOver('gitops') && (
|
|
2344
|
+
{!viewsSyncGated && mainView === 'gitops' && !isViewTakenOver('gitops') && (
|
|
2311
2345
|
<GitOpsView
|
|
2312
2346
|
namespaces={namespaces}
|
|
2313
2347
|
onOpenResource={(resource) => {
|
|
@@ -2317,12 +2351,14 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2317
2351
|
navigateToResource(resource)
|
|
2318
2352
|
}}
|
|
2319
2353
|
onClearNamespaces={clearAllNamespaces}
|
|
2320
|
-
|
|
2354
|
+
// Every GitOps "open Settings" ask is about the Argo CD connection
|
|
2355
|
+
// (diff CTA, per-resource health notice), so land on that section.
|
|
2356
|
+
onOpenSettings={() => openSettings('argocd')}
|
|
2321
2357
|
/>
|
|
2322
2358
|
)}
|
|
2323
2359
|
|
|
2324
2360
|
{/* Applications view — deployable software grouped by app/release evidence */}
|
|
2325
|
-
{mainView === 'applications' && (
|
|
2361
|
+
{!viewsSyncGated && mainView === 'applications' && (
|
|
2326
2362
|
<ApplicationsView
|
|
2327
2363
|
namespaces={namespaces}
|
|
2328
2364
|
onOpenResource={(resource) => {
|
|
@@ -2343,16 +2379,16 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2343
2379
|
)}
|
|
2344
2380
|
|
|
2345
2381
|
{/* Traffic view */}
|
|
2346
|
-
{mainView === 'traffic' && (
|
|
2382
|
+
{!viewsSyncGated && mainView === 'traffic' && (
|
|
2347
2383
|
<TrafficView namespaces={namespaces} />
|
|
2348
2384
|
)}
|
|
2349
2385
|
|
|
2350
2386
|
{/* Cost detail view */}
|
|
2351
|
-
{mainView === 'cost' && (
|
|
2387
|
+
{!viewsSyncGated && mainView === 'cost' && (
|
|
2352
2388
|
<CostView namespaces={namespaces} onBack={() => setMainView('home')} onOpenResource={navigateToResource} />
|
|
2353
2389
|
)}
|
|
2354
2390
|
|
|
2355
|
-
{mainView === 'capacity' && (
|
|
2391
|
+
{!viewsSyncGated && mainView === 'capacity' && (
|
|
2356
2392
|
<CapacityView onOpenResource={navigateToResource} />
|
|
2357
2393
|
)}
|
|
2358
2394
|
|
|
@@ -2370,7 +2406,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2370
2406
|
|
|
2371
2407
|
{/* Checks detail view. Cloud can take over fleet best practices while
|
|
2372
2408
|
the target-specific upgrade route continues to render locally. */}
|
|
2373
|
-
{mainView === 'checks' && (!isViewTakenOver('checks') || upgradeReadinessRoute) && (
|
|
2409
|
+
{!viewsSyncGated && mainView === 'checks' && (!isViewTakenOver('checks') || upgradeReadinessRoute) && (
|
|
2374
2410
|
<AuditView
|
|
2375
2411
|
namespaces={namespaces}
|
|
2376
2412
|
onNavigateToResource={navigateToResourceList}
|
|
@@ -2382,7 +2418,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2382
2418
|
Hub fleet uses; a GitOps reconciler subject routes to its detail page,
|
|
2383
2419
|
other resources open the standard resource view. Inline only when the
|
|
2384
2420
|
host hasn't taken it over. */}
|
|
2385
|
-
{mainView === 'issues' && !isViewTakenOver('issues') && (
|
|
2421
|
+
{!viewsSyncGated && mainView === 'issues' && !isViewTakenOver('issues') && (
|
|
2386
2422
|
<IssuesPane
|
|
2387
2423
|
namespaces={namespaces}
|
|
2388
2424
|
onNavigateToResource={navigateFromIssue}
|
|
@@ -2392,7 +2428,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2392
2428
|
{/* Workload full view — the standalone fullscreen route for non-list
|
|
2393
2429
|
surfaces and deep links. Expand-from-drawer is the ?full=1 overlay on
|
|
2394
2430
|
/resources instead, so it never routes here. */}
|
|
2395
|
-
{mainView === 'workload' && (
|
|
2431
|
+
{!viewsSyncGated && mainView === 'workload' && (
|
|
2396
2432
|
<WorkloadViewRoute
|
|
2397
2433
|
onNavigateToResource={(resource) => {
|
|
2398
2434
|
navigate(relatedResourcePath(resource))
|
|
@@ -2401,7 +2437,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2401
2437
|
)}
|
|
2402
2438
|
|
|
2403
2439
|
{/* Compare two resources of the same kind side-by-side */}
|
|
2404
|
-
{mainView === 'compare' && <CompareViewRoute />}
|
|
2440
|
+
{!viewsSyncGated && mainView === 'compare' && <CompareViewRoute />}
|
|
2405
2441
|
|
|
2406
2442
|
</ErrorBoundary>
|
|
2407
2443
|
</div>}
|
|
@@ -2410,7 +2446,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
|
|
|
2410
2446
|
{/* Resource detail drawer — stays mounted, expands to full-screen WorkloadView.
|
|
2411
2447
|
Gated on contentReady so it never renders over the connecting/switching
|
|
2412
2448
|
splash (which would push the centered logo off-center). */}
|
|
2413
|
-
{contentReady && resourceDrawer.shouldRender && drawerResource && (
|
|
2449
|
+
{(contentReady || shellDuringSync) && resourceDrawer.shouldRender && drawerResource && (
|
|
2414
2450
|
<ResourceDetailDrawer
|
|
2415
2451
|
resource={drawerResource}
|
|
2416
2452
|
initialTab={drawerInitialTab}
|
package/src/RadarApp.tsx
CHANGED
|
@@ -42,6 +42,7 @@ import type { TimelineSourceConfig } from "./api/timelineSource";
|
|
|
42
42
|
import { DiagnoseCustomizationProvider } from "./context/DiagnoseCustomization";
|
|
43
43
|
import type {
|
|
44
44
|
RenderDiagnoseAction,
|
|
45
|
+
RenderInvestigationRunActions,
|
|
45
46
|
DiagnoseConsentCopy,
|
|
46
47
|
} from "./context/DiagnoseCustomization";
|
|
47
48
|
import { defaultDiagnoseAction } from "./components/diagnose/LocalDiagnoseAction";
|
|
@@ -113,6 +114,8 @@ export interface RadarAppProps {
|
|
|
113
114
|
* agent-free. See ./context/DiagnoseCustomization for the render-prop shape.
|
|
114
115
|
*/
|
|
115
116
|
renderDiagnoseAction?: RenderDiagnoseAction;
|
|
117
|
+
/** Host-owned controls for the focused investigation; absent in standalone Radar. */
|
|
118
|
+
renderInvestigationRunActions?: RenderInvestigationRunActions;
|
|
116
119
|
/**
|
|
117
120
|
* Replaces the first-run consent card's trust copy. REQUIRED of any host whose
|
|
118
121
|
* backend runs the agent somewhere other than the user's own machine — the
|
|
@@ -202,6 +205,7 @@ export function RadarApp({
|
|
|
202
205
|
manageDocumentTitle = false,
|
|
203
206
|
documentTitleSuffix,
|
|
204
207
|
renderDiagnoseAction,
|
|
208
|
+
renderInvestigationRunActions,
|
|
205
209
|
diagnoseConsent,
|
|
206
210
|
initialPath,
|
|
207
211
|
onClusterLoadStateChange,
|
|
@@ -234,6 +238,7 @@ export function RadarApp({
|
|
|
234
238
|
<DiagnoseCustomizationProvider
|
|
235
239
|
value={renderDiagnoseAction ?? defaultDiagnoseAction}
|
|
236
240
|
consentCopy={diagnoseConsent}
|
|
241
|
+
renderRunActions={renderInvestigationRunActions}
|
|
237
242
|
>
|
|
238
243
|
<DiagnoseProvider
|
|
239
244
|
browserURLState={router !== "memory"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
1
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
|
|
3
3
|
// Deterministic config so the 401 handler's basename/routePath math is fixed and
|
|
4
4
|
// independent of window/env. client.ts only consumes these six exports.
|
|
@@ -67,6 +67,17 @@ function make401(authMode: string): Response {
|
|
|
67
67
|
} as unknown as Response
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// Each test re-imports ./client after resetModules, and the first import in the
|
|
71
|
+
// file is the one that transforms client.ts and everything it pulls in. That is
|
|
72
|
+
// seconds of work on a contended machine, and it would otherwise be charged to
|
|
73
|
+
// whichever test happens to run first, failing it on timeout while the rest of
|
|
74
|
+
// the suite passes in under a second. Pay it once, outside any test's budget —
|
|
75
|
+
// resetModules drops module instances but keeps the transform cache, so the
|
|
76
|
+
// per-test re-import stays cheap.
|
|
77
|
+
beforeAll(async () => {
|
|
78
|
+
await import('./client')
|
|
79
|
+
}, 120_000)
|
|
80
|
+
|
|
70
81
|
beforeEach(() => {
|
|
71
82
|
// Reset module state so the redirect gate starts fresh.
|
|
72
83
|
vi.resetModules()
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
PROM_STATUS_POLL_DISCOVERING_MS,
|
|
4
|
+
PROM_STATUS_POLL_IDLE_MS,
|
|
5
|
+
prometheusStatusRefetchInterval,
|
|
6
|
+
} from './client'
|
|
7
|
+
|
|
8
|
+
describe('prometheusStatusRefetchInterval', () => {
|
|
9
|
+
it('polls quickly while discovery is in flight or has not reported yet', () => {
|
|
10
|
+
expect(prometheusStatusRefetchInterval(undefined)).toBe(PROM_STATUS_POLL_IDLE_MS)
|
|
11
|
+
// Read before the server's run started: transient, keep polling.
|
|
12
|
+
expect(prometheusStatusRefetchInterval({ available: false, connected: false })).toBe(PROM_STATUS_POLL_DISCOVERING_MS)
|
|
13
|
+
// A run that ended without a connection carries its outcome.
|
|
14
|
+
expect(prometheusStatusRefetchInterval({ available: false, connected: false, error: 'no Prometheus service found in cluster' })).toBe(
|
|
15
|
+
PROM_STATUS_POLL_IDLE_MS,
|
|
16
|
+
)
|
|
17
|
+
expect(prometheusStatusRefetchInterval({ available: false, connected: false, discovering: true })).toBe(
|
|
18
|
+
PROM_STATUS_POLL_DISCOVERING_MS,
|
|
19
|
+
)
|
|
20
|
+
expect(prometheusStatusRefetchInterval({ available: true, connected: true, address: 'http://localhost:1' })).toBe(
|
|
21
|
+
PROM_STATUS_POLL_IDLE_MS,
|
|
22
|
+
)
|
|
23
|
+
})
|
|
24
|
+
})
|