@skyhook-io/radar-app 1.13.5 → 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.
Files changed (201) hide show
  1. package/package.json +8 -8
  2. package/src/App.tsx +60 -24
  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 +321 -16
  6. package/src/api/diagnose.ts +1 -0
  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 +179 -42
  13. package/src/components/CloudFunnelButton.tsx +107 -36
  14. package/src/components/SyncProgressPanel.tsx +73 -0
  15. package/src/components/audit/AuditView.tsx +2 -0
  16. package/src/components/cloudConnectHandoff.test.ts +140 -0
  17. package/src/components/cloudConnectHandoff.ts +124 -0
  18. package/src/components/diagnose/DiagnoseContext.tsx +2 -1
  19. package/src/components/diagnose/investigationEvidence/adapters/changes.ts +60 -0
  20. package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +573 -0
  21. package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +935 -0
  22. package/src/components/diagnose/investigationEvidence/adapters/events.ts +96 -0
  23. package/src/components/diagnose/investigationEvidence/adapters/helm.test.ts +242 -0
  24. package/src/components/diagnose/investigationEvidence/adapters/helm.ts +208 -0
  25. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +172 -0
  26. package/src/components/diagnose/investigationEvidence/adapters/logs.test.ts +283 -0
  27. package/src/components/diagnose/investigationEvidence/adapters/logs.ts +183 -0
  28. package/src/components/diagnose/investigationEvidence/adapters/permissions.test.ts +528 -0
  29. package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +292 -0
  30. package/src/components/diagnose/investigationEvidence/adapters/prometheus.test.ts +1641 -0
  31. package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +769 -0
  32. package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +54 -0
  33. package/src/components/diagnose/investigationEvidence/adapters/resource.ts +257 -0
  34. package/src/components/diagnose/investigationEvidence/adapters/topology.ts +207 -0
  35. package/src/components/diagnose/investigationEvidence/builder.ts +418 -0
  36. package/src/components/diagnose/investigationEvidence/evidenceFixtures.ts +179 -0
  37. package/src/components/diagnose/investigationEvidence/identity.test.ts +614 -0
  38. package/src/components/diagnose/investigationEvidence/identity.ts +261 -0
  39. package/src/components/diagnose/investigationEvidence/index.ts +55 -0
  40. package/src/components/diagnose/investigationEvidence/observations.ts +954 -0
  41. package/src/components/diagnose/investigationEvidence/parse.ts +467 -0
  42. package/src/components/diagnose/investigationEvidence/projection.test.ts +2703 -0
  43. package/src/components/diagnose/investigationEvidence/types.ts +512 -0
  44. package/src/components/diagnose/target.test.ts +19 -0
  45. package/src/components/diagnose/target.ts +3 -2
  46. package/src/components/gitops/GitOpsView.tsx +7 -0
  47. package/src/components/gitops/RemoteDestinationCloudHint.tsx +26 -0
  48. package/src/components/resource/PrometheusCharts.overlays.test.ts +22 -0
  49. package/src/components/resource/PrometheusCharts.tsx +4 -3
  50. package/src/components/resource/PrometheusChartsGrid.render.test.tsx +99 -0
  51. package/src/components/resource/PrometheusChartsGrid.tsx +75 -35
  52. package/src/components/resource/RightsizingStrip.test.ts +38 -0
  53. package/src/components/resource/RightsizingStrip.tsx +15 -3
  54. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +79 -0
  55. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +145 -0
  56. package/src/components/resource/WorkloadMetricsSection.render.test.tsx +351 -0
  57. package/src/components/resource/WorkloadMetricsSection.tsx +486 -0
  58. package/src/components/resource/workloadMetricValues.test.ts +65 -0
  59. package/src/components/resource/workloadMetricValues.ts +30 -0
  60. package/src/components/resources/ResourcesView.tsx +44 -6
  61. package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -1
  62. package/src/components/resources/renderers/PodRenderer.tsx +4 -0
  63. package/src/components/resources/renderers/RolloutRenderer.tsx +15 -1
  64. package/src/components/resources/renderers/WorkloadRenderer.tsx +3 -0
  65. package/src/components/rightsizing/RightsizingScanView.tsx +21 -0
  66. package/src/components/settings/SettingsDialog.tsx +94 -14
  67. package/src/components/traffic/TrafficFlowList.tsx +229 -70
  68. package/src/components/traffic/TrafficGraph.tsx +6 -6
  69. package/src/components/traffic/TrafficView.tsx +10 -9
  70. package/src/components/traffic/trafficFilters.test.ts +28 -1
  71. package/src/components/traffic/trafficFilters.ts +31 -0
  72. package/src/components/workload/WorkloadView.tsx +19 -8
  73. package/src/context/ConnectionContext.tsx +48 -3
  74. package/src/context/DiagnoseCustomization.tsx +1 -0
  75. package/src/index.css +17 -0
  76. package/src/utils/navigation.ts +1 -1
  77. package/src/utils/topology-namespace.test.ts +64 -0
  78. package/src/utils/topology-namespace.ts +18 -0
  79. package/src/components/diagnose/investigationEvidence.test.ts +0 -6617
  80. package/src/components/diagnose/investigationEvidence.ts +0 -5572
  81. package/src/components/gitops/GitOpsActions.tsx +0 -1
  82. package/src/components/gitops/GitOpsStatusBadge.tsx +0 -1
  83. package/src/components/gitops/ManagedResourcesList.tsx +0 -1
  84. package/src/components/gitops/SyncCountdown.tsx +0 -1
  85. package/src/components/gitops/index.ts +0 -4
  86. package/src/components/helm/ManifestDiffViewer.tsx +0 -69
  87. package/src/components/logs/JsonLogLine.tsx +0 -1
  88. package/src/components/logs/LogCore.tsx +0 -2
  89. package/src/components/logs/useLogBuffer.ts +0 -2
  90. package/src/components/logs/useLogSearch.ts +0 -1
  91. package/src/components/resource-drawer/ResourceDrawer.tsx +0 -216
  92. package/src/components/resources/drawer-components.tsx +0 -1
  93. package/src/components/resources/renderers/AlertRenderer.tsx +0 -1
  94. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +0 -1
  95. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +0 -1
  96. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +0 -1
  97. package/src/components/resources/renderers/CertificateRenderer.tsx +0 -1
  98. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +0 -1
  99. package/src/components/resources/renderers/ChallengeRenderer.tsx +0 -1
  100. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +0 -1
  101. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +0 -1
  102. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +0 -1
  103. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +0 -1
  104. package/src/components/resources/renderers/ConfigMapRenderer.tsx +0 -1
  105. package/src/components/resources/renderers/CronJobRenderer.tsx +0 -1
  106. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +0 -1
  107. package/src/components/resources/renderers/EventRenderer.tsx +0 -1
  108. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +0 -1
  109. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +0 -1
  110. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +0 -1
  111. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +0 -1
  112. package/src/components/resources/renderers/GatewayClassRenderer.tsx +0 -1
  113. package/src/components/resources/renderers/GatewayRenderer.tsx +0 -1
  114. package/src/components/resources/renderers/GenericRenderer.tsx +0 -1
  115. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +0 -1
  116. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +0 -1
  117. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +0 -1
  118. package/src/components/resources/renderers/IngressClassRenderer.tsx +0 -1
  119. package/src/components/resources/renderers/IngressRenderer.tsx +0 -1
  120. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +0 -1
  121. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +0 -1
  122. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +0 -1
  123. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +0 -1
  124. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +0 -1
  125. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +0 -1
  126. package/src/components/resources/renderers/JobRenderer.tsx +0 -1
  127. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +0 -1
  128. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +0 -1
  129. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +0 -1
  130. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +0 -1
  131. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +0 -1
  132. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +0 -1
  133. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +0 -1
  134. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +0 -1
  135. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +0 -1
  136. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +0 -1
  137. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +0 -1
  138. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +0 -1
  139. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +0 -1
  140. package/src/components/resources/renderers/KustomizationRenderer.tsx +0 -1
  141. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +0 -1
  142. package/src/components/resources/renderers/LeaseRenderer.tsx +0 -1
  143. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +0 -1
  144. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +0 -1
  145. package/src/components/resources/renderers/OrderRenderer.tsx +0 -1
  146. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +0 -1
  147. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +0 -1
  148. package/src/components/resources/renderers/PodMonitorRenderer.tsx +0 -1
  149. package/src/components/resources/renderers/PriorityClassRenderer.tsx +0 -1
  150. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +0 -1
  151. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +0 -1
  152. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +0 -1
  153. package/src/components/resources/renderers/SbomReportRenderer.tsx +0 -1
  154. package/src/components/resources/renderers/SealedSecretRenderer.tsx +0 -1
  155. package/src/components/resources/renderers/SecretRenderer.tsx +0 -1
  156. package/src/components/resources/renderers/SecretStoreRenderer.tsx +0 -1
  157. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +0 -1
  158. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +0 -1
  159. package/src/components/resources/renderers/StorageClassRenderer.tsx +0 -1
  160. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +0 -1
  161. package/src/components/resources/renderers/VPARenderer.tsx +0 -1
  162. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +0 -1
  163. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +0 -1
  164. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +0 -1
  165. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +0 -1
  166. package/src/components/resources/renderers/WorkflowRenderer.tsx +0 -1
  167. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +0 -1
  168. package/src/components/resources/renderers/argo-cells.tsx +0 -1
  169. package/src/components/resources/renderers/certmanager-cells.tsx +0 -1
  170. package/src/components/resources/renderers/cnpg-cells.tsx +0 -1
  171. package/src/components/resources/renderers/eso-cells.tsx +0 -1
  172. package/src/components/resources/renderers/flux-cells.tsx +0 -1
  173. package/src/components/resources/renderers/index.ts +0 -94
  174. package/src/components/resources/renderers/istio-cells.tsx +0 -1
  175. package/src/components/resources/renderers/karpenter-cells.tsx +0 -1
  176. package/src/components/resources/renderers/keda-cells.tsx +0 -1
  177. package/src/components/resources/renderers/knative-cells.tsx +0 -1
  178. package/src/components/resources/renderers/kyverno-cells.tsx +0 -1
  179. package/src/components/resources/renderers/prometheus-cells.tsx +0 -1
  180. package/src/components/resources/renderers/traefik-cells.tsx +0 -1
  181. package/src/components/resources/renderers/trivy-cells.tsx +0 -1
  182. package/src/components/resources/renderers/trivy-shared.tsx +0 -1
  183. package/src/components/resources/renderers/velero-cells.tsx +0 -1
  184. package/src/components/resources/resource-utils-argo.ts +0 -2
  185. package/src/components/resources/resource-utils-certmanager.ts +0 -2
  186. package/src/components/resources/resource-utils-eso.ts +0 -2
  187. package/src/components/resources/resource-utils-flux.ts +0 -2
  188. package/src/components/resources/resource-utils-istio.ts +0 -2
  189. package/src/components/resources/resource-utils-karpenter.ts +0 -2
  190. package/src/components/resources/resource-utils-knative.ts +0 -2
  191. package/src/components/resources/resource-utils-kyverno.ts +0 -2
  192. package/src/components/resources/resource-utils-prometheus.ts +0 -2
  193. package/src/components/resources/resource-utils-traefik.ts +0 -1
  194. package/src/components/resources/resource-utils-trivy.ts +0 -2
  195. package/src/components/resources/resource-utils-velero.ts +0 -2
  196. package/src/components/shared/EditableYamlView.tsx +0 -24
  197. package/src/components/timeline/DiffViewer.tsx +0 -1
  198. package/src/components/traffic/index.ts +0 -3
  199. package/src/components/ui/ForceDeleteConfirmDialog.tsx +0 -1
  200. package/src/components/ui/MetricsChart.tsx +0 -1
  201. package/src/components/ui/ResourceBar.tsx +0 -1
@@ -1,7 +1,7 @@
1
1
  import { createContext, useContext, useState, useCallback, useEffect, useRef, ReactNode } from 'react'
2
2
  import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
3
3
  import { getApiBase } from '../api/config'
4
- import { apiFetch } from '../api/client'
4
+ import { apiFetch, type SyncStatusSnapshot } from '../api/client'
5
5
 
6
6
  export type ConnectionStateType = 'connected' | 'disconnected' | 'connecting'
7
7
 
@@ -12,6 +12,10 @@ export interface ConnectionState {
12
12
  error?: string
13
13
  errorType?: string // config, auth, auth-rejected, auth-plugin-stuck, rbac, network, timeout, tls, unknown
14
14
  progressMessage?: string
15
+ // Per-kind readiness while the initial informer sync runs ('connecting'
16
+ // with informers started). Drives the progressive app shell — absent once
17
+ // connected or before informers exist.
18
+ syncStatus?: SyncStatusSnapshot
15
19
  }
16
20
 
17
21
  interface ConnectionStatusResponse extends ConnectionState {
@@ -157,7 +161,15 @@ export function ConnectionProvider({ children }: { children: ReactNode }) {
157
161
  if (!firstConnect || cacheWarmAtMountRef.current) {
158
162
  queryClient.invalidateQueries()
159
163
  } else {
160
- queryClient.invalidateQueries({ predicate: (q) => q.state.status === 'error' })
164
+ // Detail responses served during the progressive shell are partial —
165
+ // the server skips relationship computation while the topology cache
166
+ // is unavailable — and they resolve as successes, so the error-only
167
+ // sweep would leave an open drawer without its Related Resources
168
+ // forever (focus refetch is globally off). Refetching the resource
169
+ // detail keys is at most the open drawer, so it stays cheap.
170
+ queryClient.invalidateQueries({
171
+ predicate: (q) => q.state.status === 'error' || q.queryKey[0] === 'resource',
172
+ })
161
173
  }
162
174
  }, [queryClient])
163
175
 
@@ -195,12 +207,35 @@ export function ConnectionProvider({ children }: { children: ReactNode }) {
195
207
  // while the retry is still running; the retry's own result supersedes it.
196
208
  if (manualRetryPendingRef.current || autoRetryInFlightRef.current) return
197
209
  const current = connectionRef.current
210
+ // The progressive shell renders real resource lists while still
211
+ // 'connecting'. A query client shared across cluster mounts may hold
212
+ // another cluster's data under identical keys — drop it before the
213
+ // shell can show it (the 'connected' path repeats this for the
214
+ // full-refresh case). Deliberately ahead of the SSE-generation guard:
215
+ // live connection_state frames bump the generation and drop the poll,
216
+ // and the shell must not spend the whole sync on another cluster's rows.
217
+ if (current.state === 'connecting' && data.state === 'connecting' &&
218
+ data.syncStatus && cacheWarmAtMountRef.current) {
219
+ cacheWarmAtMountRef.current = false
220
+ // Drop only cluster-scoped data. App chrome (auth identity, kubeconfig
221
+ // contexts) is not another cluster's data, and purging it flips
222
+ // authMePending back on — which holds the whole shell on the splash
223
+ // until /auth/me refetches, delaying the first progressive paint.
224
+ const keep = new Set(['connection-status', 'auth-me', 'contexts'])
225
+ queryClient.removeQueries({ predicate: (q) => !keep.has(String(q.queryKey[0])) })
226
+ }
198
227
  if (!shouldApplyPolledConnection(
199
228
  current.state,
200
229
  data.state,
201
230
  data.sseGenerationAtStart,
202
231
  sseGenerationRef.current,
203
232
  )) {
233
+ // SSE owns the state fields, but connection_state frames don't carry
234
+ // per-kind sync progress — merge it from the poll so the progressive
235
+ // shell keeps ticking while still connecting.
236
+ if (data.state === 'connecting' && data.syncStatus) {
237
+ setConnection(prev => prev.state === 'connecting' ? { ...prev, syncStatus: data.syncStatus } : prev)
238
+ }
204
239
  return
205
240
  }
206
241
  const becameConnected = current.state !== 'connected' && data.state === 'connected'
@@ -211,11 +246,12 @@ export function ConnectionProvider({ children }: { children: ReactNode }) {
211
246
  error: data.error,
212
247
  errorType: data.errorType,
213
248
  progressMessage: data.progressMessage,
249
+ syncStatus: data.syncStatus,
214
250
  })
215
251
  if (becameConnected) {
216
252
  refreshCachesOnConnect()
217
253
  }
218
- }, [data, dataUpdatedAt, refreshCachesOnConnect])
254
+ }, [data, dataUpdatedAt, refreshCachesOnConnect, queryClient])
219
255
 
220
256
  // Retry mutation
221
257
  const retryMutation = useMutation({
@@ -231,6 +267,9 @@ export function ConnectionProvider({ children }: { children: ReactNode }) {
231
267
  state: 'connecting',
232
268
  error: undefined,
233
269
  progressMessage: 'Connecting to cluster...',
270
+ // A retry is a fresh attempt — per-kind progress from the previous
271
+ // attempt would mislabel readiness until the next poll.
272
+ syncStatus: undefined,
234
273
  }))
235
274
  },
236
275
  onSuccess: (result) => {
@@ -371,6 +410,12 @@ export function ConnectionProvider({ children }: { children: ReactNode }) {
371
410
  if (prev.state === 'connected' && status.state === 'connecting') {
372
411
  return prev
373
412
  }
413
+ // connection_state frames don't carry per-kind sync progress — keep the
414
+ // last polled snapshot while still connecting, or each SSE progress
415
+ // frame would flicker the progressive shell back to the splash.
416
+ if (status.state === 'connecting' && !status.syncStatus && prev.state === 'connecting') {
417
+ return { ...status, syncStatus: prev.syncStatus }
418
+ }
374
419
  return status
375
420
  })
376
421
 
@@ -23,6 +23,7 @@ export type RenderInvestigationRunActions = (props: {
23
23
 
24
24
  /** Render prop for the resource-level Investigate action. */
25
25
  export type RenderDiagnoseAction = (ctx: {
26
+ /** Kubernetes Kind when loaded or discovered; otherwise its API resource name. */
26
27
  kind: string;
27
28
  /** Kubernetes API group; empty means core. */
28
29
  group?: string;
package/src/index.css CHANGED
@@ -21,6 +21,23 @@
21
21
  }
22
22
 
23
23
  @layer components {
24
+ .metrics-layout {
25
+ container-type: inline-size;
26
+ container-name: metrics;
27
+ }
28
+ .metrics-chart-grid {
29
+ display: grid;
30
+ grid-template-columns: minmax(0, 1fr);
31
+ gap: 0.75rem;
32
+ }
33
+ @container metrics (min-width: 760px) {
34
+ .metrics-chart-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
35
+ .metrics-chart-wide { grid-column: 1 / -1; }
36
+ }
37
+ @container metrics (min-width: 1200px) {
38
+ .metrics-chart-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
39
+ .metrics-chart-wide { grid-column: auto; }
40
+ }
24
41
  .investigation-assessment {
25
42
  background: var(--color-investigation-assessment);
26
43
  border-color: var(--color-investigation-assessment-border);
@@ -14,7 +14,7 @@ export function searchHitToSelectedResource(hit: SearchHit): SelectedResource {
14
14
  }
15
15
 
16
16
  // Re-export shared navigation utilities from @skyhook-io/k8s-ui.
17
- export { kindToPlural, kindToPluralWithGroup, pluralToKind, refToSelectedResource, apiVersionToGroup } from '@skyhook-io/k8s-ui/utils/navigation'
17
+ export { kindToPlural, kindToPluralWithGroup, pluralToKind, knownKindForPluralWithGroup, refToSelectedResource, apiVersionToGroup } from '@skyhook-io/k8s-ui/utils/navigation'
18
18
  export type { NavigateToResource } from '@skyhook-io/k8s-ui/utils/navigation'
19
19
 
20
20
  const NETWORK_POLICY_TOPOLOGY_KINDS = new Set([
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import type { TopologyNode } from '@skyhook-io/k8s-ui/types/core'
3
+ import { isInNamespaceScope, scopeNodesToNamespaces } from './topology-namespace'
4
+
5
+ function node(id: string, kind: string, namespace?: string): TopologyNode {
6
+ return {
7
+ id,
8
+ kind,
9
+ name: id,
10
+ status: 'healthy',
11
+ data: namespace === undefined ? {} : { namespace },
12
+ } as TopologyNode
13
+ }
14
+
15
+ const nodes = [
16
+ node('web', 'Deployment', 'prod'),
17
+ node('ing', 'Ingress', 'staging'),
18
+ node('db', 'StatefulSet', 'prod'),
19
+ node('node-1', 'Node'), // cluster-scoped
20
+ node('pv-1', 'PersistentVolume'), // cluster-scoped
21
+ ]
22
+
23
+ describe('scopeNodesToNamespaces', () => {
24
+ it('returns every node when no namespace is selected', () => {
25
+ expect(scopeNodesToNamespaces(nodes, [])).toHaveLength(5)
26
+ })
27
+
28
+ it('keeps only the selected namespace, plus cluster-scoped nodes', () => {
29
+ const scoped = scopeNodesToNamespaces(nodes, ['prod'])
30
+ expect(scoped.map(n => n.id)).toEqual(['web', 'db', 'node-1', 'pv-1'])
31
+ })
32
+
33
+ it('drops a kind that exists only outside the scope', () => {
34
+ const scoped = scopeNodesToNamespaces(nodes, ['prod'])
35
+ expect(scoped.some(n => n.kind === 'Ingress')).toBe(false)
36
+ })
37
+
38
+ it('accepts several namespaces at once', () => {
39
+ const scoped = scopeNodesToNamespaces(nodes, ['prod', 'staging'])
40
+ expect(scoped).toHaveLength(5)
41
+ })
42
+
43
+ it('returns the same array identity when the scope is empty', () => {
44
+ expect(scopeNodesToNamespaces(nodes, [])).toBe(nodes)
45
+ })
46
+ })
47
+
48
+ describe('isInNamespaceScope', () => {
49
+ it('passes everything for a null scope', () => {
50
+ expect(isInNamespaceScope(node('x', 'Pod', 'any'), null)).toBe(true)
51
+ })
52
+
53
+ it('always passes a node with no namespace', () => {
54
+ expect(isInNamespaceScope(node('node-1', 'Node'), new Set(['prod']))).toBe(true)
55
+ })
56
+
57
+ it('treats an empty-string namespace as cluster-scoped', () => {
58
+ expect(isInNamespaceScope(node('n', 'Node', ''), new Set(['prod']))).toBe(true)
59
+ })
60
+
61
+ it('rejects a namespaced node outside the scope', () => {
62
+ expect(isInNamespaceScope(node('ing', 'Ingress', 'staging'), new Set(['prod']))).toBe(false)
63
+ })
64
+ })
@@ -0,0 +1,18 @@
1
+ import type { TopologyNode } from '@skyhook-io/k8s-ui/types/core'
2
+
3
+ // A node with no namespace is cluster-scoped (Node, PersistentVolume,
4
+ // Namespace, ...) and stays visible in every scope.
5
+ export function isInNamespaceScope(node: TopologyNode, nsSet: Set<string> | null): boolean {
6
+ if (!nsSet) return true
7
+ const ns = node.data?.namespace as string | undefined
8
+ return !ns || nsSet.has(ns)
9
+ }
10
+
11
+ // Shared by the topology graph and the filter sidebar so the two cannot
12
+ // disagree about which nodes are in scope. Namespace carve-out only — kind
13
+ // filtering stays on the graph.
14
+ export function scopeNodesToNamespaces(nodes: TopologyNode[], namespaces: string[]): TopologyNode[] {
15
+ if (namespaces.length === 0) return nodes
16
+ const nsSet = new Set(namespaces)
17
+ return nodes.filter(node => isInNamespaceScope(node, nsSet))
18
+ }