@skyhook-io/k8s-ui 1.12.0 → 1.13.3

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 (117) hide show
  1. package/package.json +4 -4
  2. package/src/components/applications/ApplicationDetail.tsx +2 -2
  3. package/src/components/cluster-switcher/ClusterSwitcher.tsx +32 -7
  4. package/src/components/issues/severity.ts +1 -0
  5. package/src/components/resources/ResourcesView.empty-note.test.ts +41 -0
  6. package/src/components/resources/ResourcesView.tsx +419 -9
  7. package/src/components/resources/index.ts +27 -0
  8. package/src/components/resources/kyverno-legacy-action.test.ts +98 -0
  9. package/src/components/resources/renderers/AnalysisRunRenderer.test.tsx +241 -0
  10. package/src/components/resources/renderers/AnalysisRunRenderer.tsx +166 -0
  11. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +31 -2
  12. package/src/components/resources/renderers/CNPGClusterRenderer.test.tsx +33 -0
  13. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +47 -4
  14. package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +243 -0
  15. package/src/components/resources/renderers/CNPGObjectStoreRenderer.test.tsx +99 -0
  16. package/src/components/resources/renderers/CNPGObjectStoreRenderer.tsx +235 -0
  17. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +23 -1
  18. package/src/components/resources/renderers/CNPGShared.tsx +27 -0
  19. package/src/components/resources/renderers/CalicoHostEndpointRenderer.tsx +71 -0
  20. package/src/components/resources/renderers/CalicoIPPoolRenderer.tsx +32 -0
  21. package/src/components/resources/renderers/CalicoNetworkPolicyDiagram.test.tsx +205 -0
  22. package/src/components/resources/renderers/CalicoNetworkPolicyDiagram.tsx +537 -0
  23. package/src/components/resources/renderers/CalicoNetworkPolicyRenderer.test.tsx +223 -0
  24. package/src/components/resources/renderers/CalicoNetworkPolicyRenderer.tsx +670 -0
  25. package/src/components/resources/renderers/CalicoTierRenderer.tsx +19 -0
  26. package/src/components/resources/renderers/KyvernoCELPolicyRenderers.tsx +13 -6
  27. package/src/components/resources/renderers/KyvernoExceptionRenderers.tsx +3 -2
  28. package/src/components/resources/renderers/KyvernoGlobalContextRenderer.test.ts +31 -0
  29. package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +124 -0
  30. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +28 -2
  31. package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +303 -0
  32. package/src/components/resources/renderers/LookupFailureNote.test.tsx +60 -0
  33. package/src/components/resources/renderers/LookupFailureNote.tsx +42 -0
  34. package/src/components/resources/renderers/NamespaceRenderer.test.ts +30 -0
  35. package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -3
  36. package/src/components/resources/renderers/NetworkPolicyDiagram.tsx +27 -26
  37. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +6 -3
  38. package/src/components/resources/renderers/PolicyCoverageSection.test.tsx +348 -0
  39. package/src/components/resources/renderers/PolicyCoverageSection.tsx +939 -0
  40. package/src/components/resources/renderers/PolicySection.test.tsx +59 -2
  41. package/src/components/resources/renderers/PolicySection.tsx +32 -5
  42. package/src/components/resources/renderers/RolloutRenderer.test.tsx +491 -0
  43. package/src/components/resources/renderers/RolloutRenderer.tsx +391 -92
  44. package/src/components/resources/renderers/VeleroBSLRenderer.test.tsx +374 -0
  45. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +179 -3
  46. package/src/components/resources/renderers/VeleroBackupRenderer.test.tsx +221 -0
  47. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +119 -6
  48. package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.test.tsx +69 -0
  49. package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +96 -0
  50. package/src/components/resources/renderers/VeleroRestoreRenderer.test.tsx +55 -0
  51. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +22 -4
  52. package/src/components/resources/renderers/VeleroRunMessages.test.tsx +152 -0
  53. package/src/components/resources/renderers/VeleroRunMessages.tsx +139 -0
  54. package/src/components/resources/renderers/VeleroScheduleRenderer.test.tsx +88 -0
  55. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +16 -6
  56. package/src/components/resources/renderers/calico-cells.tsx +177 -0
  57. package/src/components/resources/renderers/cnpg-cells.tsx +100 -0
  58. package/src/components/resources/renderers/index.ts +16 -1
  59. package/src/components/resources/renderers/kyverno-cells.tsx +81 -0
  60. package/src/components/resources/renderers/network-policy-peer-styles.ts +25 -0
  61. package/src/components/resources/renderers/velero-cells.tsx +29 -7
  62. package/src/components/resources/renderers/velero-dead-ends.test.tsx +142 -0
  63. package/src/components/resources/resource-utils-calico.test.ts +215 -0
  64. package/src/components/resources/resource-utils-calico.ts +261 -0
  65. package/src/components/resources/resource-utils-cnpg-declarative.test.ts +104 -0
  66. package/src/components/resources/resource-utils-cnpg-objectstore.test.ts +122 -0
  67. package/src/components/resources/resource-utils-cnpg.test.ts +54 -0
  68. package/src/components/resources/resource-utils-cnpg.ts +264 -3
  69. package/src/components/resources/resource-utils-kyverno-modern.ts +24 -0
  70. package/src/components/resources/resource-utils-kyverno-queue.test.ts +164 -0
  71. package/src/components/resources/resource-utils-kyverno-queue.ts +198 -0
  72. package/src/components/resources/resource-utils-kyverno.ts +43 -7
  73. package/src/components/resources/resource-utils-velero.test.ts +26 -0
  74. package/src/components/resources/resource-utils-velero.ts +68 -0
  75. package/src/components/resources/resource-utils.ts +98 -0
  76. package/src/components/shared/ResourceActionsBar.tsx +101 -16
  77. package/src/components/shared/ResourceRendererDispatch.test.tsx +379 -3
  78. package/src/components/shared/ResourceRendererDispatch.tsx +199 -22
  79. package/src/components/shared/index.ts +1 -1
  80. package/src/components/shared/revision-role-badges.test.ts +120 -0
  81. package/src/components/topology/K8sResourceNode.test.ts +10 -0
  82. package/src/components/topology/K8sResourceNode.tsx +22 -0
  83. package/src/components/topology/TopologyFilterSidebar.test.tsx +31 -0
  84. package/src/components/topology/TopologyFilterSidebar.tsx +5 -0
  85. package/src/components/topology/TopologyGraph.tsx +6 -6
  86. package/src/components/topology/TopologySearch.tsx +7 -0
  87. package/src/components/topology/layout.ts +3 -0
  88. package/src/components/trace/ReachabilityView.tsx +1 -0
  89. package/src/components/trace/TracePanel.tsx +13 -2
  90. package/src/components/trace/reachFixtures.ts +26 -1
  91. package/src/components/trace/reachGraphModel.test.ts +20 -0
  92. package/src/components/trace/reachGraphModel.ts +8 -1
  93. package/src/components/trace/reachOrigins.test.ts +31 -0
  94. package/src/components/trace/reachOrigins.ts +11 -1
  95. package/src/components/ui/Badge.tsx +10 -0
  96. package/src/components/ui/InClusterConsentDialog.tsx +5 -4
  97. package/src/components/ui/ScheduleValue.test.tsx +80 -0
  98. package/src/components/ui/ScheduleValue.tsx +85 -0
  99. package/src/components/ui/SelectMenu.tsx +202 -28
  100. package/src/components/ui/drawer-components.test.tsx +17 -1
  101. package/src/components/ui/drawer-components.tsx +9 -2
  102. package/src/components/ui/index.ts +2 -0
  103. package/src/components/workload/WorkloadView.tsx +5 -2
  104. package/src/types/core.ts +36 -4
  105. package/src/types/policy.ts +153 -2
  106. package/src/utils/clipboard.test.ts +86 -0
  107. package/src/utils/clipboard.ts +50 -0
  108. package/src/utils/extended-resources.ts +4 -7
  109. package/src/utils/format.test.ts +83 -1
  110. package/src/utils/format.ts +80 -1
  111. package/src/utils/inClusterConsent.test.ts +9 -0
  112. package/src/utils/inClusterConsent.ts +9 -9
  113. package/src/utils/index.ts +1 -0
  114. package/src/utils/navigation.test.ts +5 -0
  115. package/src/utils/navigation.ts +5 -0
  116. package/src/utils/resource-icons.test.ts +49 -2
  117. package/src/utils/resource-icons.ts +29 -2
@@ -10,6 +10,7 @@ import {
10
10
  getNodeStatus,
11
11
  getPVCStatus,
12
12
  getRolloutStatus,
13
+ getAnalysisRunStatus,
13
14
  getWorkflowStatus,
14
15
  getCronWorkflowStatus,
15
16
  getCertificateStatus,
@@ -51,6 +52,7 @@ import { getNodePoolStatus, getNodeClaimStatus, getEC2NodeClassStatus } from '..
51
52
  import { getScaledObjectStatus, getScaledJobStatus } from '../resources/resource-utils-keda'
52
53
  import { getServiceMonitorStatus, getPrometheusRuleStatus, getPodMonitorStatus } from '../resources/resource-utils-prometheus'
53
54
  import { getPolicyReportStatus, getKyvernoPolicyStatus } from '../resources/resource-utils-kyverno'
55
+ import { getKyvernoRequestState, getKyvernoReportStatus } from '../resources/resource-utils-kyverno-queue'
54
56
  import {
55
57
  KYVERNO_MODERN_PLURALS,
56
58
  getModernKyvernoPolicyStatus,
@@ -63,7 +65,7 @@ import {
63
65
  } from '../resources/resource-utils-kyverno-exceptions'
64
66
  import { getResourceClaimStatus, getResourceClaimTemplateStatus, getDeviceClassStatus, getResourceSliceStatus } from '../resources/resource-utils-dra'
65
67
  import { getNvidiaClusterPolicyStatus, getNvidiaDriverStatus } from '../resources/resource-utils-nvidia'
66
- import { getBackupStatus, getRestoreStatus, getScheduleStatus, getBSLStatus, isVeleroResource } from '../resources/resource-utils-velero'
68
+ import { getBackupStatus, getRestoreStatus, getScheduleStatus, getBSLStatus, getBackupRepositoryStatus, isVeleroResource } from '../resources/resource-utils-velero'
67
69
  import {
68
70
  getVirtualServiceStatus,
69
71
  getDestinationRuleStatus,
@@ -72,13 +74,19 @@ import {
72
74
  getPeerAuthenticationStatus,
73
75
  getAuthorizationPolicyStatus,
74
76
  } from '../resources/resource-utils-istio'
75
- import { getCNPGClusterStatus, getCNPGBackupStatus, getCNPGScheduledBackupStatus, getCNPGPoolerStatus, isApiGroup, CNPG_GROUP } from '../resources/resource-utils-cnpg'
77
+ import { getCNPGClusterStatus, getCNPGBackupStatus, getCNPGScheduledBackupStatus, getCNPGPoolerStatus, getCNPGObjectStoreStatus, getCNPGDeclarativeStatus, isApiGroup, CNPG_GROUP } from '../resources/resource-utils-cnpg'
76
78
  import { getExternalSecretStatus, getClusterExternalSecretStatus, getSecretStoreStatus, getClusterSecretStoreStatus } from '../resources/resource-utils-eso'
77
79
  import {
78
80
  getKnativeConditionStatus,
79
81
  getRevisionStatus,
80
82
  } from '../resources/resource-utils-knative'
81
83
  import { getHTTPProxyStatus } from '../resources/resource-utils-contour'
84
+ import {
85
+ isCalicoApiVersion,
86
+ isCalicoPolicyKind,
87
+ isCalicoStagedKubernetesNetworkPolicyKind,
88
+ isCoreNetworkPolicyKind,
89
+ } from '../resources/resource-utils-calico'
82
90
  import { getClusterStatus as getCAPIClusterStatus, getMachineStatus, getMachineDeploymentStatus, getMachineSetStatus, getMachinePoolStatus, getKCPStatus, getClusterClassStatus, getMachineHealthCheckStatus } from '../resources/resource-utils-capi'
83
91
  import { getAWSMCPStatus, getAWSMMPStatus, getAWSMachineStatus, getAWSManagedClusterStatus } from '../resources/resource-utils-aws-capi'
84
92
  import { getGCPMCPStatus, getGCPMMPStatus, getGCPMachineStatus, getGCPManagedClusterStatus } from '../resources/resource-utils-gcp-capi'
@@ -98,6 +106,7 @@ import {
98
106
  NodeRenderer,
99
107
  PVCRenderer,
100
108
  RolloutRenderer,
109
+ AnalysisRunRenderer,
101
110
  CertificateRenderer,
102
111
  WorkflowRenderer,
103
112
  PersistentVolumeRenderer,
@@ -155,12 +164,22 @@ import {
155
164
  KyvernoMutatingPolicyRenderer,
156
165
  KyvernoGeneratingPolicyRenderer,
157
166
  KyvernoDeletingPolicyRenderer,
167
+ CNPGObjectStoreRenderer,
168
+ CNPGDatabaseRenderer,
169
+ CNPGPublicationRenderer,
170
+ CNPGSubscriptionRenderer,
171
+ CNPGImageCatalogRenderer,
172
+ KyvernoGlobalContextRenderer,
173
+ KyvernoUpdateRequestRenderer,
174
+ KyvernoEphemeralReportRenderer,
175
+ getKyvernoGlobalContextStatus,
158
176
  KyvernoPolicyExceptionRenderer,
159
177
  KyvernoCleanupPolicyRenderer,
160
178
  VeleroBackupRenderer,
161
179
  VeleroRestoreRenderer,
162
180
  VeleroScheduleRenderer,
163
181
  VeleroBSLRenderer,
182
+ VeleroBackupRepositoryRenderer,
164
183
  VeleroVSLRenderer,
165
184
  CNPGClusterRenderer,
166
185
  CNPGBackupRenderer,
@@ -239,6 +258,10 @@ import {
239
258
  ResourceSliceRenderer,
240
259
  NvidiaClusterPolicyRenderer,
241
260
  NvidiaDriverRenderer,
261
+ CalicoHostEndpointRenderer,
262
+ CalicoIPPoolRenderer,
263
+ CalicoNetworkPolicyRenderer,
264
+ CalicoTierRenderer,
242
265
  } from '../resources/renderers'
243
266
  import type { ComposedRefStatus } from '../resources/renderers/CompositeRenderer'
244
267
  import {
@@ -295,6 +318,48 @@ export interface RendererOverrides {
295
318
  onNavigate?: (ref: ResourceRef) => void
296
319
  composedRefStatuses?: Map<string, ComposedRefStatus>
297
320
  }>
321
+ // CNPG Publication / Subscription: the host resolves the PostgreSQL-side
322
+ // names in their spec back to the CRs that declare them. Database and
323
+ // ImageCatalog get the reverse lookup, which the API only models one way.
324
+ CNPGImageCatalogRenderer?: React.ComponentType<{ data: any; onNavigate?: (ref: ResourceRef) => void }>
325
+ CNPGDatabaseRenderer?: React.ComponentType<{ data: any; onNavigate?: (ref: ResourceRef) => void }>
326
+ CNPGPublicationRenderer?: React.ComponentType<{ data: any; onNavigate?: (ref: ResourceRef) => void }>
327
+ CNPGSubscriptionRenderer?: React.ComponentType<{ data: any; onNavigate?: (ref: ResourceRef) => void }>
328
+ // CNPG Cluster: the host wraps it to add the reverse lookup of declarative
329
+ // objects, which needs three list fetches.
330
+ CNPGClusterRenderer?: React.ComponentType<{
331
+ data: any
332
+ onNavigate?: (ref: ResourceRef) => void
333
+ }>
334
+ // CNPG ObjectStore: the host wraps the package renderer to add the reverse
335
+ // lookup (which Clusters back up here), which needs a Cluster list fetch.
336
+ CNPGObjectStoreRenderer?: React.ComponentType<{
337
+ data: any
338
+ onNavigate?: (ref: ResourceRef) => void
339
+ }>
340
+ /** Host-injected: adds the Backups-stored-here lookup to a storage location. */
341
+ VeleroBSLRenderer?: React.ComponentType<{
342
+ data: any
343
+ onNavigate?: (ref: ResourceRef) => void
344
+ }>
345
+ /** Host-injected: adds the storage location's health to a backup, and the
346
+ * on-demand fetch for the messages behind its error/warning counts. */
347
+ VeleroBackupRenderer?: React.ComponentType<{ data: any; onNavigate?: (ref: ResourceRef) => void }>
348
+ /** Host-injected: adds the on-demand fetch for the messages behind a
349
+ * restore's error and warning counts. */
350
+ VeleroRestoreRenderer?: React.ComponentType<{ data: any; onNavigate?: (ref: ResourceRef) => void }>
351
+ // Kyverno policy coverage: the host fetches /api/policy/policies/... and
352
+ // renders the section, which the policy renderers accept as a slot. One
353
+ // override serves all six policy renderers — they differ in what the policy
354
+ // declares, not in what its results look like.
355
+ KyvernoPolicyCoverage?: React.ComponentType<{
356
+ data: any
357
+ onNavigate?: (ref: ResourceRef) => void
358
+ }>
359
+ // Work a generate / mutate-existing policy has queued and not finished. The
360
+ // requests have their own page; the count belongs here because the failure
361
+ // this catches is a backlog, and nobody watches a page that is usually empty.
362
+ KyvernoPolicyQueued?: React.ComponentType<{ data: any }>
298
363
  // ServiceAccount reverse-lookup: the host fetches /api/rbac/subject/... and
299
364
  // feeds the result into the base renderer via this wrapper.
300
365
  ServiceAccountRenderer?: React.ComponentType<{
@@ -334,6 +399,12 @@ export interface RendererOverrides {
334
399
  data: any
335
400
  onNavigate?: (ref: ResourceRef) => void
336
401
  }>
402
+ // Rollout: host probes /api/rollouts/.../capabilities and wires the
403
+ // promote / abort / skip-step mutations into the action row.
404
+ RolloutRenderer?: React.ComponentType<{
405
+ data: any
406
+ onNavigate?: (ref: ResourceRef) => void
407
+ }>
337
408
  }
338
409
 
339
410
  // Known resource types with specific renderers (module-level to avoid re-allocation)
@@ -341,11 +412,15 @@ const KNOWN_KINDS = new Set([
341
412
  'pods', 'deployments', 'statefulsets', 'daemonsets', 'replicasets',
342
413
  'services', 'endpointslices', 'ingresses', 'configmaps', 'secrets', 'jobs', 'cronjobs', 'cronworkflows',
343
414
  'hpas', 'horizontalpodautoscalers', 'nodes', 'persistentvolumeclaims',
344
- 'rollouts', 'certificates', 'workflows', 'persistentvolumes',
415
+ 'rollouts', 'analysisruns', 'certificates', 'workflows', 'persistentvolumes',
345
416
  'storageclasses', 'certificaterequests', 'clusterissuers', 'issuers',
346
417
  'orders', 'challenges',
347
418
  'gateways', 'gatewayclasses', 'httproutes', 'grpcroutes', 'tcproutes', 'tlsroutes', 'sealedsecrets', 'workflowtemplates', 'clusterworkflowtemplates',
348
419
  'networkpolicies', 'networkpolicy',
420
+ 'globalnetworkpolicies', 'globalnetworkpolicy',
421
+ 'stagednetworkpolicies', 'stagednetworkpolicy',
422
+ 'stagedglobalnetworkpolicies', 'stagedglobalnetworkpolicy',
423
+ 'stagedkubernetesnetworkpolicies', 'stagedkubernetesnetworkpolicy',
349
424
  'ciliumnetworkpolicies', 'ciliumnetworkpolicy', 'ciliumclusterwidenetworkpolicies', 'ciliumclusterwidenetworkpolicy',
350
425
  'clusternetworkpolicies', 'clusternetworkpolicy',
351
426
  'poddisruptionbudgets', 'serviceaccounts', 'namespaces',
@@ -356,6 +431,10 @@ const KNOWN_KINDS = new Set([
356
431
  'triggerauthentications', 'clustertriggerauthentications',
357
432
  'servicemonitors', 'prometheusrules', 'podmonitors',
358
433
  'policyreports', 'clusterpolicyreports', 'kyvernopolicies', 'clusterpolicies',
434
+ // The API serves the legacy namespaced kind as `policies`. `kyvernopolicies`
435
+ // is Radar's internal alias for it and is NOT a servable plural, so a URL
436
+ // built from it 400s — the drawer has to know the real one.
437
+ 'policies',
359
438
  // Kyverno modern CEL family (policies.kyverno.io) + its namespaced twins.
360
439
  'validatingpolicies', 'namespacedvalidatingpolicies',
361
440
  'imagevalidatingpolicies', 'namespacedimagevalidatingpolicies',
@@ -365,14 +444,18 @@ const KNOWN_KINDS = new Set([
365
444
  'policyexceptions', 'cleanuppolicies', 'clustercleanuppolicies',
366
445
  'resourceclaims', 'resourceclaimtemplates', 'deviceclasses', 'resourceslices',
367
446
  'nvidiadrivers',
447
+ 'hostendpoints', 'ippools', 'tiers',
368
448
  'vulnerabilityreports', 'configauditreports', 'exposedsecretreports',
369
449
  'rbacassessmentreports', 'clusterrbacassessmentreports',
370
450
  'clustercompliancereports', 'sbomreports', 'clustersbomreports',
371
451
  'infraassessmentreports', 'clusterinfraassessmentreports',
372
452
  'verticalpodautoscalers',
373
453
  'backups', 'restores', 'schedules', 'backupstoragelocations', 'volumesnapshotlocations',
454
+ 'backuprepositories',
374
455
  'externalsecrets', 'clusterexternalsecrets', 'secretstores', 'clustersecretstores',
375
- 'clusters', 'scheduledbackups', 'poolers',
456
+ 'clusters', 'scheduledbackups', 'poolers', 'objectstores',
457
+ 'globalcontextentries', 'updaterequests', 'ephemeralreports', 'clusterephemeralreports',
458
+ 'databases', 'publications', 'imagecatalogs', 'clusterimagecatalogs',
376
459
  'virtualservices', 'destinationrules', 'serviceentries',
377
460
  'peerauthentications', 'authorizationpolicies',
378
461
  'mutatingwebhookconfigurations', 'validatingwebhookconfigurations',
@@ -544,6 +627,7 @@ export function ResourceRendererDispatch({
544
627
  const isVeleroCollisionGatedKind =
545
628
  kind === 'restores' || kind === 'schedules'
546
629
  || kind === 'backupstoragelocations' || kind === 'volumesnapshotlocations'
630
+ || kind === 'backuprepositories'
547
631
  const veleroCollisionFallthrough = isVeleroCollisionGatedKind && !isVeleroResource(data)
548
632
 
549
633
  // Same rule as the Crossplane block above, for the plurals shared by two
@@ -551,15 +635,43 @@ export function ResourceRendererDispatch({
551
635
  // and friends in the wild) and `backups` (CNPG / Velero). Both render lines
552
636
  // are positively apiVersion-gated, so a third CRD with the plural matches
553
637
  // neither and needs an explicit fall-through or the drawer renders blank.
638
+ //
639
+ // The CNPG declarative kinds and the barman-cloud ObjectStore extend the same
640
+ // list: `databases`, `publications` and `subscriptions` are shipped by several
641
+ // database operators, `objectstores` is a generic enough plural for any of
642
+ // them, and `subscriptions` is Knative's as well. Adding a kind to KNOWN_KINDS
643
+ // with a positively-gated render line and forgetting this list is what makes a
644
+ // foreign CRD render a BLANK drawer rather than the generic one.
554
645
  const isGroupGatedKind =
555
646
  kind === 'clusters' || kind === 'backups' || kind === 'scheduledbackups' || kind === 'poolers'
647
+ || kind === 'objectstores' || kind === 'databases' || kind === 'publications'
648
+ || kind === 'subscriptions' || kind === 'imagecatalogs' || kind === 'clusterimagecatalogs'
649
+ || kind === 'policies'
556
650
  const isCNPGApiVersion = isApiGroup(data?.apiVersion, CNPG_GROUP)
557
651
  const groupGatedMatched =
558
652
  (kind === 'clusters' && (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'cluster.x-k8s.io')))
559
653
  || (kind === 'backups' && (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'velero.io')))
560
654
  || ((kind === 'scheduledbackups' || kind === 'poolers') && isCNPGApiVersion)
655
+ || (kind === 'objectstores' && isApiGroup(data?.apiVersion, 'barmancloud.cnpg.io'))
656
+ || ((kind === 'databases' || kind === 'publications' || kind === 'imagecatalogs'
657
+ || kind === 'clusterimagecatalogs') && isCNPGApiVersion)
658
+ || (kind === 'subscriptions'
659
+ && (isCNPGApiVersion || isApiGroup(data?.apiVersion, 'messaging.knative.dev')))
660
+ || (kind === 'policies' && isApiGroup(data?.apiVersion, 'kyverno.io'))
561
661
  const groupGatedFallthrough = isGroupGatedKind && !groupGatedMatched
562
662
 
663
+ const calicoApiVersionMatched = isCalicoApiVersion(data?.apiVersion)
664
+ const isCalicoPolicy = isCalicoPolicyKind(kind) && calicoApiVersionMatched
665
+ const isCalicoStagedKubernetesPolicy =
666
+ isCalicoPolicy && isCalicoStagedKubernetesNetworkPolicyKind(kind)
667
+ const isCoreNetworkPolicy = isCoreNetworkPolicyKind(kind, data?.apiVersion, resource.group)
668
+ const isCalicoHostEndpoint = kind === 'hostendpoints' && calicoApiVersionMatched
669
+ const isCalicoIPPool = kind === 'ippools' && calicoApiVersionMatched
670
+ const isCalicoTier = kind === 'tiers' && calicoApiVersionMatched
671
+ const calicoCollisionFallthrough = (
672
+ isCalicoPolicyKind(kind) || kind === 'hostendpoints' || kind === 'ippools' || kind === 'tiers'
673
+ ) && !isCoreNetworkPolicy && !((isCalicoPolicy || isCalicoHostEndpoint || isCalicoIPPool || isCalicoTier))
674
+
563
675
  const isKnownKind = KNOWN_KINDS.has(kind) || isCrossplaneMR || isCrossplaneClaim || isCrossplaneXR
564
676
 
565
677
  const PodComp = rendererOverrides?.PodRenderer ?? PodRenderer
@@ -568,12 +680,28 @@ export function ResourceRendererDispatch({
568
680
  const NodeComp = rendererOverrides?.NodeRenderer ?? NodeRenderer
569
681
  const ServiceComp = rendererOverrides?.ServiceRenderer ?? ServiceRenderer
570
682
  const CompositeComp = rendererOverrides?.CompositeRenderer ?? CompositeRenderer
683
+ const CNPGClusterComp = rendererOverrides?.CNPGClusterRenderer ?? CNPGClusterRenderer
684
+ const CNPGImageCatalogComp = rendererOverrides?.CNPGImageCatalogRenderer ?? CNPGImageCatalogRenderer
685
+ const CNPGDatabaseComp = rendererOverrides?.CNPGDatabaseRenderer ?? CNPGDatabaseRenderer
686
+ const CNPGPublicationComp = rendererOverrides?.CNPGPublicationRenderer ?? CNPGPublicationRenderer
687
+ const CNPGSubscriptionComp = rendererOverrides?.CNPGSubscriptionRenderer ?? CNPGSubscriptionRenderer
688
+ const CNPGObjectStoreComp = rendererOverrides?.CNPGObjectStoreRenderer ?? CNPGObjectStoreRenderer
689
+ const VeleroBSLComp = rendererOverrides?.VeleroBSLRenderer ?? VeleroBSLRenderer
690
+ const VeleroBackupComp = rendererOverrides?.VeleroBackupRenderer ?? VeleroBackupRenderer
691
+ const VeleroRestoreComp = rendererOverrides?.VeleroRestoreRenderer ?? VeleroRestoreRenderer
692
+ const KyvernoCoverageComp = rendererOverrides?.KyvernoPolicyCoverage
693
+ const kyvernoCoverage = KyvernoCoverageComp ? (
694
+ <KyvernoCoverageComp data={data} onNavigate={onNavigate} />
695
+ ) : undefined
696
+ const KyvernoQueuedComp = rendererOverrides?.KyvernoPolicyQueued
697
+ const kyvernoQueued = KyvernoQueuedComp ? <KyvernoQueuedComp data={data} /> : undefined
571
698
  const ServiceAccountComp = rendererOverrides?.ServiceAccountRenderer ?? ServiceAccountRenderer
572
699
  const RoleComp = rendererOverrides?.RoleRenderer ?? RoleRenderer
573
700
  const RoleBindingComp = rendererOverrides?.RoleBindingRenderer ?? RoleBindingRenderer
574
701
  const NamespaceComp = rendererOverrides?.NamespaceRenderer ?? NamespaceRenderer
575
702
  const HPAComp = rendererOverrides?.HPARenderer ?? HPARenderer
576
703
  const PVCComp = rendererOverrides?.PVCRenderer ?? PVCRenderer
704
+ const RolloutComp = rendererOverrides?.RolloutRenderer ?? RolloutRenderer
577
705
  const scaleBlockedBy = replicaScalers(relationships?.scalers)
578
706
 
579
707
  const sidebarContent = showCommonSections && (
@@ -613,7 +741,8 @@ export function ResourceRendererDispatch({
613
741
  {(kind === 'hpas' || kind === 'horizontalpodautoscalers') && <HPAComp data={data} onNavigate={onNavigate} hpaDiagnosis={hpaDiagnosis} />}
614
742
  {kind === 'nodes' && <NodeComp data={data} relationships={relationships} />}
615
743
  {kind === 'persistentvolumeclaims' && <PVCComp data={data} onNavigate={onNavigate} />}
616
- {kind === 'rollouts' && <RolloutRenderer data={data} />}
744
+ {kind === 'rollouts' && <RolloutComp data={data} onNavigate={onNavigate} />}
745
+ {kind === 'analysisruns' && <AnalysisRunRenderer data={data} onNavigate={onNavigate} />}
617
746
  {kind === 'certificates' && !data?.apiVersion?.includes('networking.internal.knative.dev') && <CertificateRenderer data={data} />}
618
747
  {kind === 'workflows' && <WorkflowRenderer data={data} onNavigate={onNavigate} />}
619
748
  {kind === 'persistentvolumes' && <PersistentVolumeRenderer data={data} onNavigate={onNavigate} />}
@@ -631,7 +760,9 @@ export function ResourceRendererDispatch({
631
760
  {kind === 'tlsroutes' && <SimpleRouteRenderer data={data} kind="TLSRoute" onNavigate={onNavigate} />}
632
761
  {kind === 'sealedsecrets' && <SealedSecretRenderer data={data} onNavigate={onNavigate} />}
633
762
  {(kind === 'workflowtemplates' || kind === 'clusterworkflowtemplates') && <WorkflowTemplateRenderer data={data} />}
634
- {(kind === 'networkpolicies' || kind === 'networkpolicy') && <NetworkPolicyRenderer data={data} />}
763
+ {isCoreNetworkPolicy && <NetworkPolicyRenderer data={data} />}
764
+ {isCalicoStagedKubernetesPolicy && <NetworkPolicyRenderer data={data} staged />}
765
+ {isCalicoPolicy && !isCalicoStagedKubernetesPolicy && <CalicoNetworkPolicyRenderer data={data} onNavigate={onNavigate} />}
635
766
  {(kind === 'ciliumnetworkpolicies' || kind === 'ciliumnetworkpolicy' || kind === 'ciliumclusterwidenetworkpolicies' || kind === 'ciliumclusterwidenetworkpolicy') && <CiliumNetworkPolicyRenderer data={data} />}
636
767
  {(kind === 'clusternetworkpolicies' || kind === 'clusternetworkpolicy') && <ClusterNetworkPolicyRenderer data={data} />}
637
768
  {kind === 'poddisruptionbudgets' && <PodDisruptionBudgetRenderer data={data} />}
@@ -664,33 +795,47 @@ export function ResourceRendererDispatch({
664
795
  {kind === 'prometheusrules' && <PrometheusRuleRenderer data={data} />}
665
796
  {kind === 'podmonitors' && <PodMonitorRenderer data={data} />}
666
797
  {(kind === 'policyreports' || kind === 'clusterpolicyreports') && <PolicyReportRenderer data={data} />}
667
- {(kind === 'kyvernopolicies' || (kind === 'clusterpolicies' && !data?.apiVersion?.startsWith('nvidia.com/'))) && <KyvernoPolicyRenderer data={data} />}
798
+ {(kind === 'kyvernopolicies'
799
+ || (kind === 'policies' && isApiGroup(data?.apiVersion, 'kyverno.io'))
800
+ || (kind === 'clusterpolicies' && !data?.apiVersion?.startsWith('nvidia.com/'))) && <KyvernoPolicyRenderer data={data} coverage={kyvernoCoverage} queued={kyvernoQueued} />}
668
801
  {(kind === 'clusterpolicies' && data?.apiVersion?.startsWith('nvidia.com/')) && <NvidiaClusterPolicyRenderer data={data} />}
669
802
  {/* Kyverno modern CEL family. Each Namespaced* twin shares its
670
803
  cluster-scoped counterpart's renderer — same spec, narrower scope. */}
671
- {kyvernoModernMatched && (kind === 'validatingpolicies' || kind === 'namespacedvalidatingpolicies') && <KyvernoValidatingPolicyRenderer data={data} />}
672
- {kyvernoModernMatched && (kind === 'imagevalidatingpolicies' || kind === 'namespacedimagevalidatingpolicies') && <KyvernoImageValidatingPolicyRenderer data={data} />}
673
- {kyvernoModernMatched && (kind === 'mutatingpolicies' || kind === 'namespacedmutatingpolicies') && <KyvernoMutatingPolicyRenderer data={data} />}
674
- {kyvernoModernMatched && (kind === 'generatingpolicies' || kind === 'namespacedgeneratingpolicies') && <KyvernoGeneratingPolicyRenderer data={data} />}
675
- {kyvernoModernMatched && (kind === 'deletingpolicies' || kind === 'namespaceddeletingpolicies') && <KyvernoDeletingPolicyRenderer data={data} />}
804
+ {kyvernoModernMatched && (kind === 'validatingpolicies' || kind === 'namespacedvalidatingpolicies') && <KyvernoValidatingPolicyRenderer data={data} coverage={kyvernoCoverage} />}
805
+ {kyvernoModernMatched && (kind === 'imagevalidatingpolicies' || kind === 'namespacedimagevalidatingpolicies') && <KyvernoImageValidatingPolicyRenderer data={data} coverage={kyvernoCoverage} />}
806
+ {kyvernoModernMatched && (kind === 'mutatingpolicies' || kind === 'namespacedmutatingpolicies') && <KyvernoMutatingPolicyRenderer data={data} coverage={kyvernoCoverage} />}
807
+ {kyvernoModernMatched && (kind === 'generatingpolicies' || kind === 'namespacedgeneratingpolicies') && <KyvernoGeneratingPolicyRenderer data={data} coverage={kyvernoCoverage} />}
808
+ {kyvernoModernMatched && (kind === 'deletingpolicies' || kind === 'namespaceddeletingpolicies') && <KyvernoDeletingPolicyRenderer data={data} coverage={kyvernoCoverage} />}
809
+ {kind === 'globalcontextentries' && <KyvernoGlobalContextRenderer data={data} onNavigate={onNavigate} />}
810
+ {kind === 'updaterequests' && <KyvernoUpdateRequestRenderer data={data} onNavigate={onNavigate} />}
811
+ {(kind === 'ephemeralreports' || kind === 'clusterephemeralreports') && <KyvernoEphemeralReportRenderer data={data} onNavigate={onNavigate} />}
676
812
  {policyExceptionMatched && <KyvernoPolicyExceptionRenderer data={data} />}
677
813
  {kyvernoLegacyExtraMatched && <KyvernoCleanupPolicyRenderer data={data} />}
678
814
  {kind === 'nvidiadrivers' && <NvidiaDriverRenderer data={data} />}
815
+ {isCalicoHostEndpoint && <CalicoHostEndpointRenderer data={data} onNavigate={onNavigate} />}
816
+ {isCalicoIPPool && <CalicoIPPoolRenderer data={data} />}
817
+ {isCalicoTier && <CalicoTierRenderer data={data} />}
679
818
  {/* DRA (resource.k8s.io) */}
680
819
  {kind === 'resourceclaims' && <ResourceClaimRenderer data={data} onNavigate={onNavigate} />}
681
820
  {kind === 'resourceclaimtemplates' && <ResourceClaimTemplateRenderer data={data} />}
682
821
  {kind === 'deviceclasses' && <DeviceClassRenderer data={data} />}
683
822
  {kind === 'resourceslices' && <ResourceSliceRenderer data={data} onNavigate={onNavigate} />}
823
+ {kind === 'objectstores' && isApiGroup(data.apiVersion, 'barmancloud.cnpg.io') && <CNPGObjectStoreComp data={data} onNavigate={onNavigate} />}
824
+ {kind === 'databases' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGDatabaseComp data={data} onNavigate={onNavigate} />}
825
+ {kind === 'publications' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGPublicationComp data={data} onNavigate={onNavigate} />}
826
+ {kind === 'subscriptions' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGSubscriptionComp data={data} onNavigate={onNavigate} />}
827
+ {(kind === 'imagecatalogs' || kind === 'clusterimagecatalogs') && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGImageCatalogComp data={data} onNavigate={onNavigate} />}
684
828
  {kind === 'backups' && isApiGroup(data.apiVersion, CNPG_GROUP) && <CNPGBackupRenderer data={data} onNavigate={onNavigate} />}
685
- {kind === 'backups' && isApiGroup(data.apiVersion, 'velero.io') && <VeleroBackupRenderer data={data} />}
686
- {kind === 'restores' && isVeleroResource(data) && <VeleroRestoreRenderer data={data} />}
687
- {kind === 'schedules' && isVeleroResource(data) && <VeleroScheduleRenderer data={data} />}
688
- {kind === 'backupstoragelocations' && isVeleroResource(data) && <VeleroBSLRenderer data={data} />}
829
+ {kind === 'backups' && isApiGroup(data.apiVersion, 'velero.io') && <VeleroBackupComp data={data} onNavigate={onNavigate} />}
830
+ {kind === 'restores' && isVeleroResource(data) && <VeleroRestoreComp data={data} onNavigate={onNavigate} />}
831
+ {kind === 'schedules' && isVeleroResource(data) && <VeleroScheduleRenderer data={data} onNavigate={onNavigate} />}
832
+ {kind === 'backupstoragelocations' && isVeleroResource(data) && <VeleroBSLComp data={data} onNavigate={onNavigate} />}
833
+ {kind === 'backuprepositories' && isVeleroResource(data) && <VeleroBackupRepositoryRenderer data={data} onNavigate={onNavigate} />}
689
834
  {kind === 'volumesnapshotlocations' && isVeleroResource(data) && <VeleroVSLRenderer data={data} />}
690
835
  {kind === 'externalsecrets' && <ExternalSecretRenderer data={data} onNavigate={onNavigate} />}
691
836
  {kind === 'clusterexternalsecrets' && <ClusterExternalSecretRenderer data={data} onNavigate={onNavigate} />}
692
837
  {(kind === 'secretstores' || kind === 'clustersecretstores') && <SecretStoreRenderer data={data} />}
693
- {kind === 'clusters' && isApiGroup(data?.apiVersion, CNPG_GROUP) && <CNPGClusterRenderer data={data} onNavigate={onNavigate} />}
838
+ {kind === 'clusters' && isApiGroup(data?.apiVersion, CNPG_GROUP) && <CNPGClusterComp data={data} onNavigate={onNavigate} />}
694
839
  {kind === 'clusters' && isApiGroup(data?.apiVersion, 'cluster.x-k8s.io') && <CAPIClusterRenderer data={data} onNavigate={onNavigate} />}
695
840
  {kind === 'scheduledbackups' && isApiGroup(data?.apiVersion, CNPG_GROUP) && <CNPGScheduledBackupRenderer data={data} onNavigate={onNavigate} />}
696
841
  {kind === 'poolers' && isApiGroup(data?.apiVersion, CNPG_GROUP) && <CNPGPoolerRenderer data={data} onNavigate={onNavigate} />}
@@ -757,7 +902,9 @@ export function ResourceRendererDispatch({
757
902
  {kind === 'eventtypes' && <EventTypeRenderer data={data} />}
758
903
  {kind === 'channels' && <ChannelRenderer data={data} onNavigate={onNavigate} />}
759
904
  {kind === 'inmemorychannels' && <InMemoryChannelRenderer data={data} onNavigate={onNavigate} />}
760
- {kind === 'subscriptions' && <SubscriptionRenderer data={data} onNavigate={onNavigate} />}
905
+ {/* Group-guarded: CNPG serves a `subscriptions` plural too, and without
906
+ this an unrelated PostgreSQL Subscription rendered Knative's sections. */}
907
+ {kind === 'subscriptions' && isApiGroup(data.apiVersion, 'messaging.knative.dev') && <SubscriptionRenderer data={data} onNavigate={onNavigate} />}
761
908
  {kind === 'sequences' && <SequenceRenderer data={data} onNavigate={onNavigate} />}
762
909
  {kind === 'parallels' && <ParallelRenderer data={data} onNavigate={onNavigate} />}
763
910
 
@@ -787,7 +934,7 @@ export function ResourceRendererDispatch({
787
934
  for known-plural collisions where no apiVersion-gated renderer
788
935
  matched (e.g. a Knative Configuration sharing the `configurations`
789
936
  plural with Crossplane Configuration). */}
790
- {(!isKnownKind || crossplaneCollisionFallthrough || kyvernoCollisionFallthrough || veleroCollisionFallthrough || groupGatedFallthrough) && <GenericRenderer data={data} />}
937
+ {(!isKnownKind || crossplaneCollisionFallthrough || kyvernoCollisionFallthrough || veleroCollisionFallthrough || groupGatedFallthrough || calicoCollisionFallthrough) && <GenericRenderer data={data} />}
791
938
 
792
939
  {/* Common sections - can be disabled when parent handles them separately */}
793
940
  {showCommonSections && (
@@ -865,6 +1012,7 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
865
1012
  if (k === 'nodes') return getNodeStatus(data)
866
1013
  if (k === 'persistentvolumeclaims') return getPVCStatus(data)
867
1014
  if (k === 'rollouts') return getRolloutStatus(data)
1015
+ if (k === 'analysisruns') return getAnalysisRunStatus(data)
868
1016
  if (k === 'workflows') return getWorkflowStatus(data)
869
1017
  if (k === 'cronworkflows') return getCronWorkflowStatus(data)
870
1018
  if (k === 'certificates') {
@@ -910,7 +1058,8 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
910
1058
  if (k === 'clustercompliancereports') return getClusterComplianceReportStatus(data)
911
1059
  if (k === 'sbomreports' || k === 'clustersbomreports') return getSbomReportStatus(data)
912
1060
  if (k === 'policyreports' || k === 'clusterpolicyreports') return getPolicyReportStatus(data)
913
- if (k === 'kyvernopolicies' || k === 'clusterpolicies') {
1061
+ if (k === 'kyvernopolicies' || k === 'clusterpolicies'
1062
+ || (k === 'policies' && isApiGroup(data.apiVersion, 'kyverno.io'))) {
914
1063
  if (data?.apiVersion?.startsWith('nvidia.com/')) return getNvidiaClusterPolicyStatus(data)
915
1064
  return getKyvernoPolicyStatus(data)
916
1065
  }
@@ -934,9 +1083,28 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
934
1083
  if (isApiGroup(data.apiVersion, CNPG_GROUP)) return getCNPGBackupStatus(data)
935
1084
  if (isApiGroup(data.apiVersion, 'velero.io')) return getBackupStatus(data)
936
1085
  }
1086
+ // Group-guarded: `objectstores` is a generic plural another operator could
1087
+ // claim, and a foreign CR must not borrow CNPG's recovery-window semantics.
1088
+ if (k === 'objectstores' && isApiGroup(data.apiVersion, 'barmancloud.cnpg.io')) {
1089
+ return getCNPGObjectStoreStatus(data)
1090
+ }
1091
+ // Group-guarded for the same reason as the renderers: these plurals are
1092
+ // generic enough that another operator can serve them.
1093
+ if ((k === 'databases' || k === 'publications' || k === 'subscriptions') && isApiGroup(data.apiVersion, CNPG_GROUP)) {
1094
+ return getCNPGDeclarativeStatus(data)
1095
+ }
1096
+ if (k === 'globalcontextentries') return getKyvernoGlobalContextStatus(data)
1097
+ if (k === 'updaterequests') return getKyvernoRequestState(data)
1098
+ if (k === 'ephemeralreports' || k === 'clusterephemeralreports') return getKyvernoReportStatus(data)
937
1099
  if (k === 'restores' && isVeleroResource(data)) return getRestoreStatus(data)
938
1100
  if (k === 'schedules' && isVeleroResource(data)) return getScheduleStatus(data)
939
1101
  if (k === 'backupstoragelocations' && isVeleroResource(data)) return getBSLStatus(data)
1102
+ // Repositories have table columns and raise their own issue, but had no branch
1103
+ // here — so the drawer header fell through to the raw phase and showed
1104
+ // "NotReady" beside a table cell reading "Not ready". Same object, two
1105
+ // spellings. Deliberately NOT added to KNOWN_KINDS: there is no detail
1106
+ // renderer for this kind, and listing it there suppresses GenericRenderer.
1107
+ if (k === 'backuprepositories' && isVeleroResource(data)) return getBackupRepositoryStatus(data)
940
1108
  if (k === 'externalsecrets') return getExternalSecretStatus(data)
941
1109
  if (k === 'clusterexternalsecrets') return getClusterExternalSecretStatus(data)
942
1110
  if (k === 'secretstores') return getSecretStoreStatus(data)
@@ -1007,11 +1175,20 @@ export function getResourceStatus(kind: string, data: any): { text: string; colo
1007
1175
  'knativeservices', 'knativeconfigurations', 'knativeroutes',
1008
1176
  'brokers', 'triggers',
1009
1177
  'pingsources', 'apiserversources', 'containersources', 'sinkbindings',
1010
- 'channels', 'inmemorychannels', 'subscriptions',
1178
+ 'channels', 'inmemorychannels',
1011
1179
  'sequences', 'parallels',
1012
1180
  'domainmappings', 'knativeingresses', 'knativecertificates', 'serverlessservices',
1013
1181
  ]
1014
- if (knativeConditionKinds.includes(k) || (k === 'ingresses' && data.apiVersion?.includes('networking.internal.knative.dev'))) {
1182
+ // `subscriptions` is shared with CNPG (handled above) and with any number of
1183
+ // other operators, so it is matched by group rather than by name — reading a
1184
+ // foreign CRD's conditions as Knative's is the status half of the same
1185
+ // collision the renderers guard.
1186
+ const isKnativeSubscription = k === 'subscriptions' && isApiGroup(data.apiVersion, 'messaging.knative.dev')
1187
+ if (
1188
+ knativeConditionKinds.includes(k)
1189
+ || isKnativeSubscription
1190
+ || (k === 'ingresses' && data.apiVersion?.includes('networking.internal.knative.dev'))
1191
+ ) {
1015
1192
  const status = getKnativeConditionStatus(data)
1016
1193
  return { text: status.text, color: status.color }
1017
1194
  }
@@ -1,6 +1,6 @@
1
1
  export { ResourceRendererDispatch, getResourceStatus, type RendererOverrides } from './ResourceRendererDispatch'
2
2
  export { EditableYamlView, SaveSuccessAnimation } from './EditableYamlView'
3
- export { ResourceActionsBar, RevisionHistoryDialog } from './ResourceActionsBar'
3
+ export { ResourceActionsBar, RevisionHistoryDialog, isRolloutKind } from './ResourceActionsBar'
4
4
  export { CreateResourceDialog, type CreateResourceDialogProps, type ApplyResult } from './CreateResourceDialog'
5
5
  export { HelmManagedByChip, ManagedByChip, type HelmOwnerRef } from './ManagedByChip'
6
6
  export { DetailShell, type DetailShellProps, type DetailShellTab } from './DetailShell'
@@ -0,0 +1,120 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ completePromoteAfterRollback,
4
+ offersPromoteAfterRollback,
5
+ revisionRoleBadges,
6
+ } from './ResourceActionsBar'
7
+ import type { WorkloadRevision } from '../../types/core'
8
+
9
+ describe('offersPromoteAfterRollback', () => {
10
+ it('offers the option for both promotion styles — rollback alone lands neither', () => {
11
+ expect(offersPromoteAfterRollback('rollouts', true)).toBe(true)
12
+ expect(offersPromoteAfterRollback('Rollout', true)).toBe(true)
13
+ })
14
+
15
+ it('withholds it when the host reports promote-full is denied', () => {
16
+ expect(offersPromoteAfterRollback('rollouts', false)).toBe(false)
17
+ })
18
+
19
+ it('never offers it for plain workloads, whose rollback lands immediately', () => {
20
+ for (const kind of ['deployments', 'statefulsets', 'daemonsets']) {
21
+ expect(offersPromoteAfterRollback(kind, true)).toBe(false)
22
+ }
23
+ })
24
+ })
25
+
26
+ function revision(overrides: Partial<WorkloadRevision>): WorkloadRevision {
27
+ return { number: 1, image: 'web:v1', createdAt: '2026-08-07T00:00:00Z', ...overrides }
28
+ }
29
+
30
+ describe('revisionRoleBadges', () => {
31
+ it('labels a Deployment revision Current in the healthy tone', () => {
32
+ expect(revisionRoleBadges(revision({ isCurrent: true }), false)).toEqual([
33
+ { label: 'Current', tone: 'status-healthy' },
34
+ ])
35
+ })
36
+
37
+ it('gives a non-current revision no badge', () => {
38
+ expect(revisionRoleBadges(revision({}), false)).toEqual([])
39
+ expect(revisionRoleBadges(revision({}), true)).toEqual([])
40
+ })
41
+
42
+ it('splits Rolling out from Stable while a canary is mid-flight', () => {
43
+ const rolling = revisionRoleBadges(revision({ number: 5, isCurrent: true, isStable: false }), true)
44
+ expect(rolling).toEqual([{ label: 'Rolling out', tone: 'status-degraded' }])
45
+
46
+ const stable = revisionRoleBadges(revision({ number: 4, isStable: true }), true)
47
+ expect(stable).toEqual([
48
+ { label: 'Stable', tone: 'status-healthy', tip: 'Serving stable traffic — an abort reverts here' },
49
+ ])
50
+ })
51
+
52
+ it('collapses to a single Current badge once the canary stabilizes', () => {
53
+ expect(revisionRoleBadges(revision({ isCurrent: true, isStable: true }), true)).toEqual([
54
+ { label: 'Current', tone: 'status-healthy' },
55
+ ])
56
+ })
57
+
58
+ // A Rollout paused at step 0 reports current and stable as the same revision.
59
+ it('never emits both Stable and Rolling out for one revision', () => {
60
+ for (const isStable of [true, false, undefined]) {
61
+ const labels = revisionRoleBadges(revision({ isCurrent: true, isStable }), true).map((b) => b.label)
62
+ expect(labels).not.toContain('Stable')
63
+ expect(labels).toHaveLength(1)
64
+ }
65
+ })
66
+ })
67
+
68
+ describe('completePromoteAfterRollback', () => {
69
+ it('does not resolve until promote-full settles', async () => {
70
+ let release: () => void = () => {}
71
+ const gate = new Promise<void>((resolve) => {
72
+ release = resolve
73
+ })
74
+ const pending: boolean[] = []
75
+
76
+ let done = false
77
+ const run = completePromoteAfterRollback(
78
+ () => gate,
79
+ (p) => pending.push(p),
80
+ ).then(() => {
81
+ done = true
82
+ })
83
+
84
+ // Yield the microtask queue: if it did not await, done would already be true.
85
+ await Promise.resolve()
86
+ expect(done).toBe(false)
87
+ expect(pending).toEqual([true])
88
+
89
+ release()
90
+ await run
91
+ expect(done).toBe(true)
92
+ expect(pending).toEqual([true, false])
93
+ })
94
+
95
+ // A rejected promote must not stop the dialog from closing — the rollback itself
96
+ // already succeeded, and the failure is reported by the mutation's toast.
97
+ it('swallows a promote-full rejection and clears the pending flag', async () => {
98
+ const pending: boolean[] = []
99
+ await expect(
100
+ completePromoteAfterRollback(
101
+ () => Promise.reject(new Error('403 forbidden')),
102
+ (p) => pending.push(p),
103
+ ),
104
+ ).resolves.toBeUndefined()
105
+ expect(pending).toEqual([true, false])
106
+ })
107
+
108
+ it('supports a void-returning callback', async () => {
109
+ const pending: boolean[] = []
110
+ let called = false
111
+ await completePromoteAfterRollback(
112
+ () => {
113
+ called = true
114
+ },
115
+ (p) => pending.push(p),
116
+ )
117
+ expect(called).toBe(true)
118
+ expect(pending).toEqual([true, false])
119
+ })
120
+ })
@@ -8,4 +8,14 @@ describe('baseSubtitle', () => {
8
8
  expect(baseSubtitle('ServiceAccount', {})).toBe('Workload identity')
9
9
  expect(baseSubtitle('ServiceMonitor', { endpointCount: 1 })).toBe('1 scrape endpoint')
10
10
  })
11
+
12
+ it('describes a Calico staged deletion as a removal, not as selecting everything', () => {
13
+ // A staged deletion carries no selector, because the Calico API forbids one.
14
+ // Falling through to the empty-selector default would label the broadest
15
+ // possible protection onto a policy that is being taken away.
16
+ expect(baseSubtitle('CalicoStagedNetworkPolicy', { stagedAction: 'Delete' })).toBe('staged deletion')
17
+ expect(baseSubtitle('CalicoStagedGlobalNetworkPolicy', { stagedAction: 'Delete' })).toBe('staged deletion')
18
+ expect(baseSubtitle('CalicoStagedNetworkPolicy', { stagedAction: 'Set', selector: "app == 'web'" })).toBe("app == 'web'")
19
+ expect(baseSubtitle('CalicoNetworkPolicy', {})).toBe('all workloads')
20
+ })
11
21
  })
@@ -249,6 +249,11 @@ export const NODE_DIMENSIONS: Record<
249
249
  KubeadmControlPlane: { width: 300, height: 84 },
250
250
  ClusterClass: { width: 280, height: 84 },
251
251
  MachineHealthCheck: { width: 300, height: 84 },
252
+ CalicoNetworkPolicy: { width: 300, height: 84 },
253
+ CalicoGlobalNetworkPolicy: { width: 300, height: 84 },
254
+ CalicoStagedNetworkPolicy: { width: 300, height: 84 },
255
+ CalicoStagedGlobalNetworkPolicy: { width: 300, height: 84 },
256
+ CalicoStagedKubernetesNetworkPolicy: { width: 300, height: 84 },
252
257
  };
253
258
 
254
259
  // Status indicator color (for dot and left bar) - uses centralized severity colors
@@ -367,6 +372,18 @@ export function baseSubtitle(kind: NodeKind, nodeData: Record<string, unknown>):
367
372
  const port = nodeData.port;
368
373
  return port ? `${svcType} :${port}` : svcType;
369
374
  }
375
+ case "CalicoNetworkPolicy":
376
+ case "CalicoGlobalNetworkPolicy":
377
+ case "CalicoStagedNetworkPolicy":
378
+ case "CalicoStagedGlobalNetworkPolicy":
379
+ case "CalicoStagedKubernetesNetworkPolicy": {
380
+ // A staged deletion carries no selector; reading that as "all workloads"
381
+ // would show a removal as the broadest possible protection.
382
+ if (String(nodeData.stagedAction ?? "").toLowerCase() === "delete") {
383
+ return "staged deletion";
384
+ }
385
+ return (nodeData.selector as string) || "all workloads";
386
+ }
370
387
  case "Ingress":
371
388
  return (nodeData.hostname as string) || "No host";
372
389
  case "Gateway": {
@@ -443,6 +460,11 @@ export function baseSubtitle(kind: NodeKind, nodeData: Record<string, unknown>):
443
460
  const instanceType = nodeData.instanceType as string;
444
461
  return instanceType || "";
445
462
  }
463
+ case "AnalysisRun": {
464
+ const phase = (nodeData.phase as string) || "Unknown";
465
+ const trigger = nodeData.trigger as string;
466
+ return trigger ? `${trigger} • ${phase}` : phase;
467
+ }
446
468
  case "Internet":
447
469
  return "";
448
470
  default: