@skyhook-io/radar-app 1.13.5 → 1.14.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.
Files changed (242) hide show
  1. package/package.json +9 -9
  2. package/src/App.tsx +66 -28
  3. package/src/api/client.authRedirect.test.ts +12 -1
  4. package/src/api/client.prometheusStatus.test.ts +24 -0
  5. package/src/api/client.ts +324 -16
  6. package/src/api/diagnose.ts +32 -1
  7. package/src/api/drain-plan.test.ts +86 -0
  8. package/src/api/quotas.test.ts +26 -0
  9. package/src/api/quotas.ts +20 -0
  10. package/src/api/workloadMetrics.test.ts +40 -0
  11. package/src/api/workloadMetrics.ts +68 -0
  12. package/src/components/CloudConnectFlow.tsx +343 -46
  13. package/src/components/CloudFunnelButton.tsx +110 -37
  14. package/src/components/ConnectionErrorView.tsx +9 -10
  15. package/src/components/DebugOverlay.tsx +10 -6
  16. package/src/components/SyncProgressPanel.tsx +73 -0
  17. package/src/components/audit/AuditView.tsx +2 -0
  18. package/src/components/cloudConnectHandoff.test.ts +245 -0
  19. package/src/components/cloudConnectHandoff.ts +276 -0
  20. package/src/components/curl/ServiceCurlButton.tsx +19 -26
  21. package/src/components/diagnose/AgentCase.tsx +18 -5
  22. package/src/components/diagnose/AnalysisStory.test.tsx +308 -0
  23. package/src/components/diagnose/AnalysisStory.tsx +564 -0
  24. package/src/components/diagnose/DiagnoseContext.test.ts +10 -0
  25. package/src/components/diagnose/DiagnoseContext.tsx +26 -8
  26. package/src/components/diagnose/DiagnoseSurface.tsx +20 -14
  27. package/src/components/diagnose/InvestigationEvidencePane.story.test.tsx +771 -0
  28. package/src/components/diagnose/InvestigationEvidencePane.test.tsx +8 -33
  29. package/src/components/diagnose/InvestigationEvidencePane.tsx +809 -168
  30. package/src/components/diagnose/InvestigationResourceEvidence.test.tsx +30 -0
  31. package/src/components/diagnose/InvestigationResourceEvidence.tsx +20 -0
  32. package/src/components/diagnose/InvestigationView.tsx +493 -529
  33. package/src/components/diagnose/investigationCase.test.tsx +267 -129
  34. package/src/components/diagnose/investigationCase.ts +122 -77
  35. package/src/components/diagnose/investigationEvidence/adapters/changes.ts +60 -0
  36. package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +573 -0
  37. package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +935 -0
  38. package/src/components/diagnose/investigationEvidence/adapters/events.ts +96 -0
  39. package/src/components/diagnose/investigationEvidence/adapters/helm.test.ts +242 -0
  40. package/src/components/diagnose/investigationEvidence/adapters/helm.ts +208 -0
  41. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +172 -0
  42. package/src/components/diagnose/investigationEvidence/adapters/logs.test.ts +283 -0
  43. package/src/components/diagnose/investigationEvidence/adapters/logs.ts +183 -0
  44. package/src/components/diagnose/investigationEvidence/adapters/permissions.test.ts +528 -0
  45. package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +292 -0
  46. package/src/components/diagnose/investigationEvidence/adapters/prometheus.test.ts +1641 -0
  47. package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +769 -0
  48. package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +81 -0
  49. package/src/components/diagnose/investigationEvidence/adapters/resource.ts +285 -0
  50. package/src/components/diagnose/investigationEvidence/adapters/topology.ts +207 -0
  51. package/src/components/diagnose/investigationEvidence/builder.ts +427 -0
  52. package/src/components/diagnose/investigationEvidence/evidenceFixtures.ts +179 -0
  53. package/src/components/diagnose/investigationEvidence/identity.test.ts +632 -0
  54. package/src/components/diagnose/investigationEvidence/identity.ts +261 -0
  55. package/src/components/diagnose/investigationEvidence/index.ts +55 -0
  56. package/src/components/diagnose/investigationEvidence/observations.ts +978 -0
  57. package/src/components/diagnose/investigationEvidence/parse.ts +467 -0
  58. package/src/components/diagnose/investigationEvidence/projection.test.ts +2736 -0
  59. package/src/components/diagnose/investigationEvidence/types.ts +514 -0
  60. package/src/components/diagnose/investigationEvidencePresentation.test.ts +2 -0
  61. package/src/components/diagnose/investigationEvidencePresentation.ts +3 -0
  62. package/src/components/diagnose/investigationState.test.ts +316 -58
  63. package/src/components/diagnose/investigationState.ts +257 -44
  64. package/src/components/diagnose/investigationStory.test.ts +161 -0
  65. package/src/components/diagnose/investigationStory.ts +207 -0
  66. package/src/components/diagnose/parts.test.tsx +9 -6
  67. package/src/components/diagnose/parts.tsx +977 -193
  68. package/src/components/diagnose/storyParts.test.tsx +426 -0
  69. package/src/components/diagnose/target.test.ts +19 -0
  70. package/src/components/diagnose/target.ts +3 -2
  71. package/src/components/diagnose/toolCallLabel.test.ts +51 -0
  72. package/src/components/diagnose/toolCallLabel.ts +135 -0
  73. package/src/components/diagnose/useDisclosureReveal.ts +10 -11
  74. package/src/components/gitops/GitOpsView.tsx +7 -0
  75. package/src/components/gitops/RemoteDestinationCloudHint.tsx +26 -0
  76. package/src/components/helm/HelmCompareRoute.tsx +18 -4
  77. package/src/components/helm/HelmReleaseDrawer.tsx +11 -26
  78. package/src/components/helm/InstallWizard.tsx +8 -3
  79. package/src/components/home/MCPSetupDialog.tsx +15 -9
  80. package/src/components/portforward/PortForwardManager.tsx +6 -13
  81. package/src/components/resource/PrometheusCharts.overlays.test.ts +22 -0
  82. package/src/components/resource/PrometheusCharts.tsx +4 -3
  83. package/src/components/resource/PrometheusChartsGrid.render.test.tsx +99 -0
  84. package/src/components/resource/PrometheusChartsGrid.tsx +75 -35
  85. package/src/components/resource/RightsizingStrip.test.ts +38 -0
  86. package/src/components/resource/RightsizingStrip.tsx +15 -3
  87. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +79 -0
  88. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +145 -0
  89. package/src/components/resource/WorkloadMetricsSection.render.test.tsx +351 -0
  90. package/src/components/resource/WorkloadMetricsSection.tsx +480 -0
  91. package/src/components/resource/workloadMetricValues.test.ts +65 -0
  92. package/src/components/resource/workloadMetricValues.ts +30 -0
  93. package/src/components/resources/PodFilePreview.tsx +3 -3
  94. package/src/components/resources/ResourcesView.tsx +44 -6
  95. package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -1
  96. package/src/components/resources/renderers/PodRenderer.tsx +4 -0
  97. package/src/components/resources/renderers/RolloutRenderer.tsx +15 -1
  98. package/src/components/resources/renderers/ServiceRenderer.tsx +2 -1
  99. package/src/components/resources/renderers/WorkloadRenderer.tsx +3 -0
  100. package/src/components/rightsizing/RightsizingScanView.tsx +21 -0
  101. package/src/components/settings/SettingsDialog.tsx +98 -16
  102. package/src/components/traffic/TrafficFlowList.tsx +229 -70
  103. package/src/components/traffic/TrafficGraph.tsx +6 -6
  104. package/src/components/traffic/TrafficView.tsx +10 -9
  105. package/src/components/traffic/trafficFilters.test.ts +28 -1
  106. package/src/components/traffic/trafficFilters.ts +31 -0
  107. package/src/components/ui/CommandPalette.tsx +10 -6
  108. package/src/components/ui/DiagnosticsOverlay.tsx +10 -6
  109. package/src/components/ui/Disclosure.tsx +47 -0
  110. package/src/components/ui/Markdown.tsx +23 -11
  111. package/src/components/ui/ShortcutHelpOverlay.tsx +3 -1
  112. package/src/components/ui/UpdateNotification.tsx +18 -2
  113. package/src/components/workload/WorkloadView.tsx +19 -8
  114. package/src/context/ConnectionContext.tsx +48 -3
  115. package/src/context/DiagnoseCustomization.tsx +1 -0
  116. package/src/index.css +36 -6
  117. package/src/utils/navigation.ts +1 -1
  118. package/src/utils/topology-namespace.test.ts +64 -0
  119. package/src/utils/topology-namespace.ts +18 -0
  120. package/src/components/diagnose/investigationEvidence.test.ts +0 -6617
  121. package/src/components/diagnose/investigationEvidence.ts +0 -5572
  122. package/src/components/gitops/GitOpsActions.tsx +0 -1
  123. package/src/components/gitops/GitOpsStatusBadge.tsx +0 -1
  124. package/src/components/gitops/ManagedResourcesList.tsx +0 -1
  125. package/src/components/gitops/SyncCountdown.tsx +0 -1
  126. package/src/components/gitops/index.ts +0 -4
  127. package/src/components/helm/ManifestDiffViewer.tsx +0 -69
  128. package/src/components/logs/JsonLogLine.tsx +0 -1
  129. package/src/components/logs/LogCore.tsx +0 -2
  130. package/src/components/logs/useLogBuffer.ts +0 -2
  131. package/src/components/logs/useLogSearch.ts +0 -1
  132. package/src/components/resource-drawer/ResourceDrawer.tsx +0 -216
  133. package/src/components/resources/drawer-components.tsx +0 -1
  134. package/src/components/resources/renderers/AlertRenderer.tsx +0 -1
  135. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +0 -1
  136. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +0 -1
  137. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +0 -1
  138. package/src/components/resources/renderers/CertificateRenderer.tsx +0 -1
  139. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +0 -1
  140. package/src/components/resources/renderers/ChallengeRenderer.tsx +0 -1
  141. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +0 -1
  142. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +0 -1
  143. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +0 -1
  144. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +0 -1
  145. package/src/components/resources/renderers/ConfigMapRenderer.tsx +0 -1
  146. package/src/components/resources/renderers/CronJobRenderer.tsx +0 -1
  147. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +0 -1
  148. package/src/components/resources/renderers/EventRenderer.tsx +0 -1
  149. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +0 -1
  150. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +0 -1
  151. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +0 -1
  152. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +0 -1
  153. package/src/components/resources/renderers/GatewayClassRenderer.tsx +0 -1
  154. package/src/components/resources/renderers/GatewayRenderer.tsx +0 -1
  155. package/src/components/resources/renderers/GenericRenderer.tsx +0 -1
  156. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +0 -1
  157. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +0 -1
  158. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +0 -1
  159. package/src/components/resources/renderers/IngressClassRenderer.tsx +0 -1
  160. package/src/components/resources/renderers/IngressRenderer.tsx +0 -1
  161. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +0 -1
  162. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +0 -1
  163. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +0 -1
  164. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +0 -1
  165. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +0 -1
  166. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +0 -1
  167. package/src/components/resources/renderers/JobRenderer.tsx +0 -1
  168. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +0 -1
  169. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +0 -1
  170. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +0 -1
  171. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +0 -1
  172. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +0 -1
  173. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +0 -1
  174. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +0 -1
  175. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +0 -1
  176. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +0 -1
  177. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +0 -1
  178. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +0 -1
  179. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +0 -1
  180. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +0 -1
  181. package/src/components/resources/renderers/KustomizationRenderer.tsx +0 -1
  182. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +0 -1
  183. package/src/components/resources/renderers/LeaseRenderer.tsx +0 -1
  184. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +0 -1
  185. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +0 -1
  186. package/src/components/resources/renderers/OrderRenderer.tsx +0 -1
  187. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +0 -1
  188. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +0 -1
  189. package/src/components/resources/renderers/PodMonitorRenderer.tsx +0 -1
  190. package/src/components/resources/renderers/PriorityClassRenderer.tsx +0 -1
  191. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +0 -1
  192. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +0 -1
  193. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +0 -1
  194. package/src/components/resources/renderers/SbomReportRenderer.tsx +0 -1
  195. package/src/components/resources/renderers/SealedSecretRenderer.tsx +0 -1
  196. package/src/components/resources/renderers/SecretRenderer.tsx +0 -1
  197. package/src/components/resources/renderers/SecretStoreRenderer.tsx +0 -1
  198. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +0 -1
  199. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +0 -1
  200. package/src/components/resources/renderers/StorageClassRenderer.tsx +0 -1
  201. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +0 -1
  202. package/src/components/resources/renderers/VPARenderer.tsx +0 -1
  203. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +0 -1
  204. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +0 -1
  205. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +0 -1
  206. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +0 -1
  207. package/src/components/resources/renderers/WorkflowRenderer.tsx +0 -1
  208. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +0 -1
  209. package/src/components/resources/renderers/argo-cells.tsx +0 -1
  210. package/src/components/resources/renderers/certmanager-cells.tsx +0 -1
  211. package/src/components/resources/renderers/cnpg-cells.tsx +0 -1
  212. package/src/components/resources/renderers/eso-cells.tsx +0 -1
  213. package/src/components/resources/renderers/flux-cells.tsx +0 -1
  214. package/src/components/resources/renderers/index.ts +0 -94
  215. package/src/components/resources/renderers/istio-cells.tsx +0 -1
  216. package/src/components/resources/renderers/karpenter-cells.tsx +0 -1
  217. package/src/components/resources/renderers/keda-cells.tsx +0 -1
  218. package/src/components/resources/renderers/knative-cells.tsx +0 -1
  219. package/src/components/resources/renderers/kyverno-cells.tsx +0 -1
  220. package/src/components/resources/renderers/prometheus-cells.tsx +0 -1
  221. package/src/components/resources/renderers/traefik-cells.tsx +0 -1
  222. package/src/components/resources/renderers/trivy-cells.tsx +0 -1
  223. package/src/components/resources/renderers/trivy-shared.tsx +0 -1
  224. package/src/components/resources/renderers/velero-cells.tsx +0 -1
  225. package/src/components/resources/resource-utils-argo.ts +0 -2
  226. package/src/components/resources/resource-utils-certmanager.ts +0 -2
  227. package/src/components/resources/resource-utils-eso.ts +0 -2
  228. package/src/components/resources/resource-utils-flux.ts +0 -2
  229. package/src/components/resources/resource-utils-istio.ts +0 -2
  230. package/src/components/resources/resource-utils-karpenter.ts +0 -2
  231. package/src/components/resources/resource-utils-knative.ts +0 -2
  232. package/src/components/resources/resource-utils-kyverno.ts +0 -2
  233. package/src/components/resources/resource-utils-prometheus.ts +0 -2
  234. package/src/components/resources/resource-utils-traefik.ts +0 -1
  235. package/src/components/resources/resource-utils-trivy.ts +0 -2
  236. package/src/components/resources/resource-utils-velero.ts +0 -2
  237. package/src/components/shared/EditableYamlView.tsx +0 -24
  238. package/src/components/timeline/DiffViewer.tsx +0 -1
  239. package/src/components/traffic/index.ts +0 -3
  240. package/src/components/ui/ForceDeleteConfirmDialog.tsx +0 -1
  241. package/src/components/ui/MetricsChart.tsx +0 -1
  242. 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.13.5",
3
+ "version": "1.14.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",
@@ -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.2.8",
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",
@@ -41,7 +41,7 @@
41
41
  "yaml": "^2.9.0"
42
42
  },
43
43
  "peerDependencies": {
44
- "@skyhook-io/k8s-ui": ">=1.14.2",
44
+ "@skyhook-io/k8s-ui": ">=1.15.0",
45
45
  "@tanstack/react-query": ">=5",
46
46
  "@xyflow/react": ">=12.0.0",
47
47
  "clsx": ">=2",
@@ -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.2.18",
64
- "@types/react-dom": "^19.2.3",
65
- "@vitejs/plugin-react": "^6.1.0",
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.2.8",
77
- "react-dom": "^19.2.8",
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.67.0",
82
+ "typescript-eslint": "^8.70.0",
83
83
  "vite": "^8.2.2"
84
84
  },
85
85
  "sideEffects": [
package/src/App.tsx CHANGED
@@ -30,7 +30,7 @@ import { ApplicationsView } from './components/applications/ApplicationsView'
30
30
  import { HelmReleaseDrawer } from './components/helm/HelmReleaseDrawer'
31
31
  import { PortForwardProvider, PortForwardIndicator, PortForwardPanel } from './components/portforward/PortForwardManager'
32
32
  import { DockProvider, BottomDock, useDock, useDockReservedHeight, useOpenLocalTerminal } from './components/dock'
33
- import { DURATION_DOCK } from '@skyhook-io/k8s-ui/utils/animation'
33
+ import { DURATION_DOCK, overlayExitMs } from '@skyhook-io/k8s-ui/utils/animation'
34
34
  import { ContextSwitcher } from './components/ContextSwitcher'
35
35
  import { NamespaceSwitcher, type NamespaceSwitcherHandle } from './components/NamespaceSwitcher'
36
36
  import { CloudFunnelButton } from './components/CloudFunnelButton'
@@ -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'
@@ -678,9 +680,11 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
678
680
  // Animation hooks for smooth mount/unmount transitions
679
681
  const resourceDrawer = useAnimatedUnmount(!!routeSelectedResource, 300)
680
682
  const helmDrawer = useAnimatedUnmount(!!(mainView === 'helm' && selectedHelmRelease), 300)
681
- const helpOverlay = useAnimatedUnmount(showHelp, 300)
682
- const commandPaletteAnim = useAnimatedUnmount(showCommandPalette, 300)
683
- const diagnosticsOverlay = useAnimatedUnmount(showDiagnostics, 300)
683
+ // Dialog-kind overlays wait their exit duration (shorter than the entrance);
684
+ // drawers keep the symmetric slide.
685
+ const helpOverlay = useAnimatedUnmount(showHelp, overlayExitMs('dialog'))
686
+ const commandPaletteAnim = useAnimatedUnmount(showCommandPalette, overlayExitMs('dialog'))
687
+ const diagnosticsOverlay = useAnimatedUnmount(showDiagnostics, overlayExitMs('dialog'))
684
688
 
685
689
  // Hold last valid values so drawers can animate out before data disappears
686
690
  const lastResourceRef = useRef(routeSelectedResource)
@@ -992,6 +996,15 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
992
996
  const contentReady = !isSwitching && !authMePending &&
993
997
  !(authMe?.authEnabled && !authMe?.username) && connection.state === 'connected'
994
998
 
999
+ // Progressive shell during the initial informer sync: once the server
1000
+ // publishes per-kind sync progress, render the app instead of the splash.
1001
+ // Resource views serve kinds as they become ready; everything else shows
1002
+ // the sync progress panel until 'connected'.
1003
+ const shellDuringSync = !isSwitching && !authMePending &&
1004
+ !(authMe?.authEnabled && !authMe?.username) &&
1005
+ connection.state === 'connecting' && !!connection.syncStatus?.kinds?.length
1006
+ const viewsSyncGated = shellDuringSync && mainView !== 'resources'
1007
+
995
1008
  const { clusterLoadState, showHomeClusterLoadFallback, clusterLoadInitial } = useClusterLoadState({
996
1009
  namespaces,
997
1010
  mainView,
@@ -1245,16 +1258,22 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
1245
1258
  const clusterConnectionState = connection.state
1246
1259
  const clusterConnected = clusterConnectionState === 'connected'
1247
1260
  const liveUpdatesDisconnected = clusterConnected && !eventStreamConnected && !eventStreamConnecting
1261
+ // During the progressive shell, the header label carries the global sync
1262
+ // progress — the one place that explains why some views are open and
1263
+ // others still loading.
1264
+ const syncProgressLabel = connection.syncStatus
1265
+ ? `Loading cluster data — ${connection.syncStatus.criticalSynced + connection.syncStatus.deferredSynced} of ${connection.syncStatus.criticalTotal + connection.syncStatus.deferredTotal} ready`
1266
+ : 'Connecting'
1248
1267
  const headerConnectionLabel =
1249
1268
  clusterConnectionState === 'disconnected' ? 'Disconnected' :
1250
- clusterConnectionState === 'connecting' ? 'Connecting' :
1269
+ clusterConnectionState === 'connecting' ? syncProgressLabel :
1251
1270
  liveUpdatesDisconnected ? 'Live updates disconnected' :
1252
1271
  clusterLoadState.loading ? `Connected — ${clusterLoadState.message}` :
1253
1272
  crdDiscoveryStatus === 'discovering' ? 'Connected — discovering Custom Resources...' :
1254
1273
  'Connected'
1255
1274
  const headerConnectionDisplayLabel =
1256
1275
  clusterConnectionState === 'disconnected' ? 'Disconnected' :
1257
- clusterConnectionState === 'connecting' ? 'Connecting' :
1276
+ clusterConnectionState === 'connecting' ? syncProgressLabel :
1258
1277
  liveUpdatesDisconnected ? 'Live updates disconnected' :
1259
1278
  showClusterWarmupLabel ? clusterLoadState.message :
1260
1279
  crdDiscoveryStatus === 'discovering' ? 'Discovering Custom Resources…' :
@@ -1604,8 +1623,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
1604
1623
  // Filter by namespace (client-side) and by visible kinds
1605
1624
  const nsSet = namespaces.length > 0 ? new Set(namespaces) : null
1606
1625
  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))
1626
+ effectiveKinds.has(node.kind) && isInNamespaceScope(node, nsSet)
1609
1627
  )
1610
1628
  const filteredNodeIds = new Set(filteredNodes.map(n => n.id))
1611
1629
 
@@ -1630,6 +1648,16 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
1630
1648
  }
1631
1649
  }, [displayedTopology, visibleKinds, namespaces, topologyMode])
1632
1650
 
1651
+ // Namespace-scoped but NOT kind-filtered: the sidebar derives its kind list
1652
+ // and visible/hidden footer counts from this prop, so handing it the
1653
+ // kind-filtered graph nodes would drop every hidden kind from the list and
1654
+ // "hidden" would always read zero. Reads displayedTopology so the counts
1655
+ // freeze with the graph while paused.
1656
+ const filterSidebarNodes = useMemo(() => {
1657
+ if (!displayedTopology) return []
1658
+ return scopeNodesToNamespaces(displayedTopology.nodes, namespaces)
1659
+ }, [displayedTopology, namespaces])
1660
+
1633
1661
  // Cluster Audit findings, joined onto topology nodes by the audit key the
1634
1662
  // backend stamps on each node (data.auditKey). Only badge-worthy findings
1635
1663
  // reach the graph; the raw auditDanger/auditWarning property names remain at
@@ -2021,7 +2049,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2021
2049
  {/* Connecting view — shown during initial connection or retry.
2022
2050
  Icon is pane-anchored so its screen position matches the
2023
2051
  host hub splash across cross-document transitions. */}
2024
- {!isSwitching && !(authMe?.authEnabled && !authMe?.username) && connection.state === 'connecting' && (
2052
+ {!isSwitching && !(authMe?.authEnabled && !authMe?.username) && connection.state === 'connecting' && !shellDuringSync && (
2025
2053
  <PaneLoader
2026
2054
  label="Connecting to cluster"
2027
2055
  className="flex-1 min-h-0 bg-theme-base"
@@ -2082,13 +2110,21 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2082
2110
  {/* inert while a fullscreen detail overlay covers the views — keeps the
2083
2111
  retained background list out of the focus order + a11y tree (the visual
2084
2112
  cover already blocks pointer events). */}
2085
- {contentReady && <div className="flex-1 flex overflow-hidden" inert={expandedView}>
2113
+ {(contentReady || shellDuringSync) && <div className="flex-1 flex overflow-hidden" inert={expandedView}>
2086
2114
  {/* Search included, not just the path: selection inside a view rides in
2087
2115
  the query (?resource=, ?release=), so a path-only key would still
2088
2116
  strand a crash on the view that produced it. */}
2089
2117
  <ErrorBoundary resetKey={location.pathname + location.search}>
2118
+ {/* Initial sync in progress: views that need the full cluster dataset
2119
+ show per-kind progress instead; resource views work as kinds sync. */}
2120
+ {viewsSyncGated && connection.syncStatus && (
2121
+ <SyncProgressPanel
2122
+ syncStatus={connection.syncStatus}
2123
+ onNavigateToKind={(key) => navigate({ pathname: `/resources/${key}` })}
2124
+ />
2125
+ )}
2090
2126
  {/* Home dashboard */}
2091
- {mainView === 'home' && (
2127
+ {!viewsSyncGated && mainView === 'home' && (
2092
2128
  <HomeView
2093
2129
  namespaces={namespaces}
2094
2130
  topology={topology}
@@ -2147,7 +2183,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2147
2183
  )}
2148
2184
 
2149
2185
  {/* Topology view */}
2150
- {mainView === 'topology' && (
2186
+ {!viewsSyncGated && mainView === 'topology' && (
2151
2187
  <>
2152
2188
  {topology?.requiresNamespaceFilter && namespaces.length === 0 ? (
2153
2189
  /* Large cluster: prompt user to select a namespace */
@@ -2182,14 +2218,14 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2182
2218
  <>
2183
2219
  {/* Filter sidebar */}
2184
2220
  <TopologyFilterSidebar
2185
- nodes={topology?.nodes || []}
2221
+ nodes={filterSidebarNodes}
2186
2222
  visibleKinds={visibleKinds}
2187
2223
  onToggleKind={handleToggleKind}
2188
2224
  onShowAll={handleShowAllKinds}
2189
2225
  onHideAll={handleHideAllKinds}
2190
2226
  collapsed={filterSidebarCollapsed}
2191
2227
  onToggleCollapse={() => setFilterSidebarCollapsed(prev => !prev)}
2192
- hiddenKinds={topology?.hiddenKinds}
2228
+ hiddenKinds={displayedTopology?.hiddenKinds}
2193
2229
  onEnableHiddenKind={(kind) => {
2194
2230
  setVisibleKinds(prev => new Set(prev).add(kind as NodeKind))
2195
2231
  console.log(`[topology] User requested to show hidden kind: ${kind}`)
@@ -2275,7 +2311,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2275
2311
  )}
2276
2312
 
2277
2313
  {/* Timeline view */}
2278
- {mainView === 'timeline' && (
2314
+ {!viewsSyncGated && mainView === 'timeline' && (
2279
2315
  <TimelineView
2280
2316
  namespaces={namespaces}
2281
2317
  onResourceClick={(resource) => {
@@ -2293,7 +2329,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2293
2329
  />
2294
2330
  )}
2295
2331
 
2296
- {mainView === 'helm' && (
2332
+ {!viewsSyncGated && mainView === 'helm' && (
2297
2333
  <HelmView
2298
2334
  namespaces={namespaces}
2299
2335
  selectedRelease={selectedHelmRelease}
@@ -2301,13 +2337,13 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2301
2337
  />
2302
2338
  )}
2303
2339
 
2304
- {mainView === 'helmCompare' && (
2340
+ {!viewsSyncGated && mainView === 'helmCompare' && (
2305
2341
  <HelmCompareRoute />
2306
2342
  )}
2307
2343
 
2308
2344
  {/* GitOps view (inline only when the host hasn't taken it over — see
2309
2345
  the takeover splash below). */}
2310
- {mainView === 'gitops' && !isViewTakenOver('gitops') && (
2346
+ {!viewsSyncGated && mainView === 'gitops' && !isViewTakenOver('gitops') && (
2311
2347
  <GitOpsView
2312
2348
  namespaces={namespaces}
2313
2349
  onOpenResource={(resource) => {
@@ -2317,12 +2353,14 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2317
2353
  navigateToResource(resource)
2318
2354
  }}
2319
2355
  onClearNamespaces={clearAllNamespaces}
2320
- onOpenSettings={() => openSettings()}
2356
+ // Every GitOps "open Settings" ask is about the Argo CD connection
2357
+ // (diff CTA, per-resource health notice), so land on that section.
2358
+ onOpenSettings={() => openSettings('argocd')}
2321
2359
  />
2322
2360
  )}
2323
2361
 
2324
2362
  {/* Applications view — deployable software grouped by app/release evidence */}
2325
- {mainView === 'applications' && (
2363
+ {!viewsSyncGated && mainView === 'applications' && (
2326
2364
  <ApplicationsView
2327
2365
  namespaces={namespaces}
2328
2366
  onOpenResource={(resource) => {
@@ -2343,16 +2381,16 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2343
2381
  )}
2344
2382
 
2345
2383
  {/* Traffic view */}
2346
- {mainView === 'traffic' && (
2384
+ {!viewsSyncGated && mainView === 'traffic' && (
2347
2385
  <TrafficView namespaces={namespaces} />
2348
2386
  )}
2349
2387
 
2350
2388
  {/* Cost detail view */}
2351
- {mainView === 'cost' && (
2389
+ {!viewsSyncGated && mainView === 'cost' && (
2352
2390
  <CostView namespaces={namespaces} onBack={() => setMainView('home')} onOpenResource={navigateToResource} />
2353
2391
  )}
2354
2392
 
2355
- {mainView === 'capacity' && (
2393
+ {!viewsSyncGated && mainView === 'capacity' && (
2356
2394
  <CapacityView onOpenResource={navigateToResource} />
2357
2395
  )}
2358
2396
 
@@ -2370,7 +2408,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2370
2408
 
2371
2409
  {/* Checks detail view. Cloud can take over fleet best practices while
2372
2410
  the target-specific upgrade route continues to render locally. */}
2373
- {mainView === 'checks' && (!isViewTakenOver('checks') || upgradeReadinessRoute) && (
2411
+ {!viewsSyncGated && mainView === 'checks' && (!isViewTakenOver('checks') || upgradeReadinessRoute) && (
2374
2412
  <AuditView
2375
2413
  namespaces={namespaces}
2376
2414
  onNavigateToResource={navigateToResourceList}
@@ -2382,7 +2420,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2382
2420
  Hub fleet uses; a GitOps reconciler subject routes to its detail page,
2383
2421
  other resources open the standard resource view. Inline only when the
2384
2422
  host hasn't taken it over. */}
2385
- {mainView === 'issues' && !isViewTakenOver('issues') && (
2423
+ {!viewsSyncGated && mainView === 'issues' && !isViewTakenOver('issues') && (
2386
2424
  <IssuesPane
2387
2425
  namespaces={namespaces}
2388
2426
  onNavigateToResource={navigateFromIssue}
@@ -2392,7 +2430,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2392
2430
  {/* Workload full view — the standalone fullscreen route for non-list
2393
2431
  surfaces and deep links. Expand-from-drawer is the ?full=1 overlay on
2394
2432
  /resources instead, so it never routes here. */}
2395
- {mainView === 'workload' && (
2433
+ {!viewsSyncGated && mainView === 'workload' && (
2396
2434
  <WorkloadViewRoute
2397
2435
  onNavigateToResource={(resource) => {
2398
2436
  navigate(relatedResourcePath(resource))
@@ -2401,7 +2439,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2401
2439
  )}
2402
2440
 
2403
2441
  {/* Compare two resources of the same kind side-by-side */}
2404
- {mainView === 'compare' && <CompareViewRoute />}
2442
+ {!viewsSyncGated && mainView === 'compare' && <CompareViewRoute />}
2405
2443
 
2406
2444
  </ErrorBoundary>
2407
2445
  </div>}
@@ -2410,7 +2448,7 @@ function AppInner({ manageDocumentTitle = false, documentTitleSuffix, onClusterL
2410
2448
  {/* Resource detail drawer — stays mounted, expands to full-screen WorkloadView.
2411
2449
  Gated on contentReady so it never renders over the connecting/switching
2412
2450
  splash (which would push the centered logo off-center). */}
2413
- {contentReady && resourceDrawer.shouldRender && drawerResource && (
2451
+ {(contentReady || shellDuringSync) && resourceDrawer.shouldRender && drawerResource && (
2414
2452
  <ResourceDetailDrawer
2415
2453
  resource={drawerResource}
2416
2454
  initialTab={drawerInitialTab}
@@ -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
+ })