@skyhook-io/k8s-ui 1.8.10 → 1.8.12

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 (168) hide show
  1. package/package.json +7 -6
  2. package/src/assets/gitops/argocd.png +0 -0
  3. package/src/assets/gitops/flux.svg +7 -0
  4. package/src/components/applications/AppChips.tsx +10 -0
  5. package/src/components/applications/ApplicationDetail.test.tsx +697 -0
  6. package/src/components/applications/ApplicationDetail.tsx +3269 -474
  7. package/src/components/applications/ApplicationsView.tsx +62 -10
  8. package/src/components/applications/applications-view.test.tsx +47 -0
  9. package/src/components/applications/index.ts +1 -1
  10. package/src/components/audit/AuditAlerts.tsx +14 -10
  11. package/src/components/checks/ChecksView.tsx +2 -3
  12. package/src/components/checks/severity.ts +14 -10
  13. package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -2
  14. package/src/components/compare/CompareResourcePicker.tsx +2 -2
  15. package/src/components/gitops/GitOpsDetailLayout.tsx +13 -3
  16. package/src/components/gitops/GitOpsGraphFilterRail.tsx +2 -1
  17. package/src/components/gitops/GitOpsTableView.tsx +13 -6
  18. package/src/components/gitops/ManagedResourcesList.tsx +8 -11
  19. package/src/components/gitops/SyncCountdown.tsx +26 -17
  20. package/src/components/gitops/SyncOptionsDialog.test.ts +48 -0
  21. package/src/components/gitops/SyncOptionsDialog.tsx +190 -46
  22. package/src/components/gitops/insights/ArgoResourceDiff.tsx +172 -0
  23. package/src/components/gitops/insights/GitOpsInsightViews.tsx +474 -121
  24. package/src/components/gitops/insights/index.ts +1 -0
  25. package/src/components/gitops/insights/insights-helpers.test.ts +102 -1
  26. package/src/components/gitops/insights/insights-helpers.ts +74 -1
  27. package/src/components/gitops/tree/GitOpsTreeGraph.tsx +10 -2
  28. package/src/components/issues/IssuesView.tsx +91 -52
  29. package/src/components/issues/ResourceIssuesSection.test.tsx +39 -6
  30. package/src/components/issues/ResourceIssuesSection.tsx +46 -144
  31. package/src/components/issues/index.ts +3 -0
  32. package/src/components/issues/issue-timing.ts +53 -0
  33. package/src/components/issues/issues.test.ts +102 -1
  34. package/src/components/issues/severity.ts +3 -0
  35. package/src/components/logs/LogCore.tsx +14 -4
  36. package/src/components/logs/WorkloadLogsViewer.tsx +31 -6
  37. package/src/components/logs/useLogStream.ts +8 -1
  38. package/src/components/namespace-switcher/NamespacePicker.tsx +86 -107
  39. package/src/components/resources/ResourcesSidebar.test.tsx +71 -0
  40. package/src/components/resources/ResourcesSidebar.tsx +46 -46
  41. package/src/components/resources/ResourcesView.selected-kind.test.ts +143 -0
  42. package/src/components/resources/ResourcesView.tsx +233 -51
  43. package/src/components/resources/renderers/AWSMachineRenderer.tsx +3 -2
  44. package/src/components/resources/renderers/AWSManagedClusterRenderer.tsx +3 -2
  45. package/src/components/resources/renderers/AWSManagedControlPlaneRenderer.tsx +3 -2
  46. package/src/components/resources/renderers/AWSManagedMachinePoolRenderer.tsx +3 -2
  47. package/src/components/resources/renderers/AzureMachineRenderer.tsx +3 -2
  48. package/src/components/resources/renderers/AzureManagedControlPlaneRenderer.tsx +3 -2
  49. package/src/components/resources/renderers/AzureManagedMachinePoolRenderer.tsx +3 -2
  50. package/src/components/resources/renderers/CAPIClusterRenderer.tsx +3 -2
  51. package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +3 -2
  52. package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +3 -2
  53. package/src/components/resources/renderers/CAPIMachineHealthCheckRenderer.tsx +3 -2
  54. package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +3 -2
  55. package/src/components/resources/renderers/CAPIMachineRenderer.tsx +3 -2
  56. package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +3 -2
  57. package/src/components/resources/renderers/CertificateRenderer.tsx +3 -2
  58. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +3 -2
  59. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
  60. package/src/components/resources/renderers/CompositeRenderer.tsx +3 -2
  61. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +99 -0
  62. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +3 -2
  63. package/src/components/resources/renderers/GCPMachineRenderer.tsx +3 -2
  64. package/src/components/resources/renderers/GCPManagedControlPlaneRenderer.tsx +3 -2
  65. package/src/components/resources/renderers/GCPManagedMachinePoolRenderer.tsx +3 -2
  66. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +7 -3
  67. package/src/components/resources/renderers/GatewayClassRenderer.tsx +3 -2
  68. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +7 -3
  69. package/src/components/resources/renderers/JobRenderer.tsx +3 -1
  70. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +3 -2
  71. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +3 -2
  72. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +3 -2
  73. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +3 -2
  74. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +3 -2
  75. package/src/components/resources/renderers/ManagedResourceRenderer.tsx +3 -2
  76. package/src/components/resources/renderers/PVCRenderer.tsx +3 -2
  77. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +2 -2
  78. package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
  79. package/src/components/resources/renderers/SealedSecretRenderer.tsx +3 -2
  80. package/src/components/resources/renderers/SecretRenderer.tsx +2 -0
  81. package/src/components/resources/renderers/SecretStoreRenderer.tsx +3 -2
  82. package/src/components/resources/renderers/ServiceRenderer.tsx +58 -40
  83. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +7 -3
  84. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
  85. package/src/components/resources/renderers/WorkflowRenderer.test.tsx +43 -0
  86. package/src/components/resources/renderers/WorkflowRenderer.tsx +60 -78
  87. package/src/components/resources/renderers/index.ts +1 -0
  88. package/src/components/resources/resource-utils-argo.test.ts +18 -1
  89. package/src/components/resources/resource-utils-argo.ts +8 -0
  90. package/src/components/resources/resource-utils-capi.ts +1 -1
  91. package/src/components/resources/resource-utils.ts +31 -0
  92. package/src/components/shared/DetailShell.tsx +2 -2
  93. package/src/components/shared/ResourceActionsBar.tsx +142 -111
  94. package/src/components/shared/ResourceRendererDispatch.tsx +33 -4
  95. package/src/components/shared/UnifiedDiff.tsx +51 -0
  96. package/src/components/shared/index.ts +1 -0
  97. package/src/components/timeline/TimelineList.tsx +375 -354
  98. package/src/components/timeline/TimelineStrip.test.tsx +194 -0
  99. package/src/components/timeline/TimelineStrip.tsx +836 -0
  100. package/src/components/timeline/TimelineSwimlanes.test.tsx +1028 -0
  101. package/src/components/timeline/TimelineSwimlanes.tsx +2498 -593
  102. package/src/components/timeline/TimelineToolbar.test.tsx +320 -0
  103. package/src/components/timeline/TimelineToolbar.tsx +759 -0
  104. package/src/components/timeline/TimelineToolbarState.test.tsx +88 -0
  105. package/src/components/timeline/index.ts +23 -0
  106. package/src/components/timeline/scrubber-math.test.ts +235 -0
  107. package/src/components/timeline/scrubber-math.ts +249 -0
  108. package/src/components/timeline/shared.test.tsx +219 -0
  109. package/src/components/timeline/shared.tsx +232 -447
  110. package/src/components/timeline/timeline-filters.test.ts +196 -0
  111. package/src/components/timeline/timeline-filters.ts +180 -0
  112. package/src/components/timeline/timeline-lane-sort.test.ts +97 -0
  113. package/src/components/timeline/timeline-lane-sort.ts +77 -0
  114. package/src/components/timeline/timeline-live.test.ts +131 -0
  115. package/src/components/timeline/timeline-live.ts +77 -0
  116. package/src/components/topology/GroupNode.tsx +9 -2
  117. package/src/components/topology/K8sResourceNode.test.ts +11 -0
  118. package/src/components/topology/K8sResourceNode.tsx +387 -243
  119. package/src/components/topology/TopologyBreadcrumb.tsx +30 -0
  120. package/src/components/topology/TopologyControls.tsx +3 -1
  121. package/src/components/topology/TopologyFilterSidebar.tsx +9 -0
  122. package/src/components/topology/TopologyGraph.tsx +149 -90
  123. package/src/components/topology/TopologyOverlayBar.tsx +30 -0
  124. package/src/components/topology/TopologySearch.tsx +25 -17
  125. package/src/components/topology/index.ts +2 -0
  126. package/src/components/topology/layout.ts +10 -2
  127. package/src/components/topology/topology.css +0 -92
  128. package/src/components/ui/Badge.tsx +6 -1
  129. package/src/components/ui/CodeViewer.tsx +2 -2
  130. package/src/components/ui/Collapse.tsx +59 -0
  131. package/src/components/ui/ConfirmDialog.tsx +7 -3
  132. package/src/components/ui/DialogPortal.tsx +31 -9
  133. package/src/components/ui/Input.tsx +24 -0
  134. package/src/components/ui/MultiSelectPicker.test.tsx +101 -0
  135. package/src/components/ui/MultiSelectPicker.tsx +167 -0
  136. package/src/components/ui/PaneLoader.tsx +10 -2
  137. package/src/components/ui/ResourceBar.tsx +68 -25
  138. package/src/components/ui/SearchBox.tsx +18 -4
  139. package/src/components/ui/SearchPillInput.tsx +2 -2
  140. package/src/components/ui/SelectMenu.tsx +81 -0
  141. package/src/components/ui/drawer-components.tsx +48 -32
  142. package/src/components/ui/index.ts +6 -0
  143. package/src/components/ui/severity-tone.ts +11 -4
  144. package/src/components/workload/ResourceDetailDrawer.tsx +6 -2
  145. package/src/components/workload/WorkloadView.tsx +2706 -475
  146. package/src/components/workload/index.ts +1 -1
  147. package/src/theme/tailwind-theme.css +1 -0
  148. package/src/theme/variables.css +2 -1
  149. package/src/types/core.ts +57 -4
  150. package/src/types/gitops-insights.ts +69 -1
  151. package/src/types/gitops.ts +3 -3
  152. package/src/utils/application-history.test.ts +538 -0
  153. package/src/utils/application-history.ts +257 -0
  154. package/src/utils/application-topology.test.ts +198 -0
  155. package/src/utils/application-topology.ts +261 -0
  156. package/src/utils/applications.test.ts +201 -1
  157. package/src/utils/applications.ts +1182 -393
  158. package/src/utils/index.ts +3 -0
  159. package/src/utils/navigation.test.ts +32 -1
  160. package/src/utils/navigation.ts +70 -0
  161. package/src/utils/resource-hierarchy.test.ts +1024 -1
  162. package/src/utils/resource-hierarchy.ts +925 -267
  163. package/src/utils/resource-icons.ts +2 -0
  164. package/src/utils/topology-neighborhood.test.ts +202 -1
  165. package/src/utils/topology-neighborhood.ts +194 -13
  166. package/src/utils/workflow-execution.test.ts +556 -0
  167. package/src/utils/workflow-execution.ts +814 -0
  168. package/src/utils/workload-colors.ts +2 -3
@@ -0,0 +1,30 @@
1
+ interface TopologyBreadcrumbProps {
2
+ /** The single active namespace to show. */
3
+ namespace: string
4
+ /** When set, renders a clickable "All Namespaces" crumb that clears the filter. */
5
+ onClear?: () => void
6
+ }
7
+
8
+ /**
9
+ * Namespace-context crumb for the topology overlay bar, shown when the graph is
10
+ * scoped to a single namespace. Place it in the host's overlay-bar composition
11
+ * (e.g. above the search) so it reads as page context.
12
+ */
13
+ export function TopologyBreadcrumb({ namespace, onClear }: TopologyBreadcrumbProps) {
14
+ return (
15
+ <div className="flex items-center gap-1.5 pointer-events-auto">
16
+ {onClear && (
17
+ <button
18
+ onClick={onClear}
19
+ className="text-xs text-theme-text-tertiary hover:text-theme-text-secondary transition-colors"
20
+ >
21
+ All Namespaces
22
+ </button>
23
+ )}
24
+ {onClear && <span className="text-xs text-theme-text-tertiary">/</span>}
25
+ <span className="text-xs font-medium text-theme-text-secondary bg-theme-surface/80 backdrop-blur-sm border border-theme-border/50 rounded-md px-2 py-0.5">
26
+ {namespace}
27
+ </span>
28
+ </div>
29
+ )
30
+ }
@@ -80,7 +80,9 @@ export function TopologyControls({
80
80
  }
81
81
 
82
82
  return (
83
- <div className="absolute top-4 right-4 z-10 flex items-center gap-2">
83
+ // Layout-neutral (positioned by the host's overlay bar): `relative` anchors
84
+ // its own dropdowns; `pointer-events-auto` inside the pointer-events-none bar.
85
+ <div className="relative flex items-center gap-2 pointer-events-auto">
84
86
  {/* Freshness/liveness status — backed for legibility over the canvas but
85
87
  borderless + divided off, so it reads as a status, not another control. */}
86
88
  {leadingSlot && (
@@ -45,13 +45,22 @@ const RESOURCE_KINDS: {
45
45
  { kind: 'PodGroup', label: 'Pod Group', icon: getTopologyIcon('PodGroup'), color: 'text-lime-400', category: 'workloads' },
46
46
  { kind: 'Job', label: 'Job', icon: getTopologyIcon('Job'), color: 'text-orange-400', category: 'workloads' },
47
47
  { kind: 'CronJob', label: 'CronJob', icon: getTopologyIcon('CronJob'), color: 'text-orange-300', category: 'workloads' },
48
+ { kind: 'Workflow', label: 'Workflow', icon: getTopologyIcon('Workflow'), color: 'text-purple-400', category: 'workloads' },
49
+ { kind: 'CronWorkflow', label: 'CronWorkflow', icon: getTopologyIcon('CronWorkflow'), color: 'text-purple-300', category: 'workloads' },
48
50
 
49
51
  // Config
50
52
  { kind: 'ConfigMap', label: 'ConfigMap', icon: getTopologyIcon('ConfigMap'), color: 'text-amber-400', category: 'config' },
51
53
  { kind: 'Secret', label: 'Secret', icon: getTopologyIcon('Secret'), color: 'text-red-400', category: 'config' },
54
+ { kind: 'ServiceAccount', label: 'Service Account', icon: getTopologyIcon('ServiceAccount'), color: 'text-fuchsia-400', category: 'config' },
55
+ { kind: 'SealedSecret', label: 'Sealed Secret', icon: getTopologyIcon('SealedSecret'), color: 'text-violet-400', category: 'config' },
56
+ { kind: 'ServiceMonitor', label: 'Service Monitor', icon: getTopologyIcon('ServiceMonitor'), color: 'text-sky-400', category: 'config' },
57
+ { kind: 'PodMonitor', label: 'Pod Monitor', icon: getTopologyIcon('PodMonitor'), color: 'text-sky-400', category: 'config' },
58
+ { kind: 'WorkflowTemplate', label: 'WorkflowTemplate', icon: getTopologyIcon('WorkflowTemplate'), color: 'text-purple-300', category: 'config' },
59
+ { kind: 'ClusterWorkflowTemplate', label: 'ClusterWorkflowTemplate', icon: getTopologyIcon('ClusterWorkflowTemplate'), color: 'text-purple-300', category: 'config' },
52
60
 
53
61
  // Scaling
54
62
  { kind: 'HorizontalPodAutoscaler', label: 'HPA', icon: getTopologyIcon('HorizontalPodAutoscaler'), color: 'text-pink-400', category: 'scaling' },
63
+ { kind: 'ScaledJob', label: 'ScaledJob', icon: getTopologyIcon('ScaledJob'), color: 'text-pink-300', category: 'scaling' },
55
64
 
56
65
  // Knative
57
66
  { kind: 'KnativeService', label: 'Knative Service', icon: getTopologyIcon('KnativeService'), color: 'text-fuchsia-400', category: 'custom' },
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
1
+ import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import {
3
3
  ReactFlow,
4
4
  ReactFlowProvider,
@@ -14,6 +14,7 @@ import {
14
14
  type NodeTypes,
15
15
  type NodeChange,
16
16
  type Viewport,
17
+ type FitViewOptions,
17
18
  BackgroundVariant,
18
19
  MarkerType,
19
20
  } from '@xyflow/react'
@@ -22,6 +23,8 @@ import { toCanvas } from 'html-to-image'
22
23
 
23
24
  import { AlertTriangle, ChevronsDownUp, ChevronsUpDown, Download, Info, Layers, LayoutGrid, Loader2, Maximize, Minus, Pause, Play, Plus, RotateCw, Shield } from 'lucide-react'
24
25
  import { PaneLoader } from '../ui/PaneLoader'
26
+ import { TopologyOverlayBar } from './TopologyOverlayBar'
27
+ import { Input } from '../ui/Input'
25
28
  import { Tooltip } from '../ui/Tooltip'
26
29
  import { useToast } from '../ui/Toast'
27
30
  import { useRegisterShortcuts } from '../../hooks/useKeyboardShortcuts'
@@ -184,10 +187,6 @@ interface TopologyGraphProps {
184
187
  onTogglePause?: () => void
185
188
  /** Called when user clicks "maximize" on a namespace group — sets namespace filter to just that namespace */
186
189
  onMaximizeNamespace?: (namespace: string) => void
187
- /** Shown as a breadcrumb label when viewing a single namespace */
188
- namespaceBreadcrumb?: string
189
- /** Called when breadcrumb "back" is clicked to return to all-namespace view */
190
- onClearNamespace?: () => void
191
190
  /** Serialized namespace filter — when this changes, reset groupLevels for fresh smart default */
192
191
  namespacesKey?: string
193
192
  /** Node to pan/zoom the canvas to. Bump focusNonce to re-trigger for the same id. */
@@ -201,6 +200,13 @@ interface TopologyGraphProps {
201
200
  /** Hover a node → reports its TopologyNode (null on leave). Drives the rail's
202
201
  * reciprocal highlight. */
203
202
  onNodeHover?: (node: TopologyNode | null) => void
203
+ /** Toggle a stable Deployment's normally-collapsed ReplicaSet tier. */
204
+ onToggleReplicaSets?: (ownerID: string) => void
205
+ /** Padding reserved around fit-to-view operations for overlaid UI. */
206
+ fitViewPadding?: FitViewOptions['padding']
207
+ /** Host overlay content (e.g. a search + controls row), stacked above the
208
+ * topology's own status banners in the overlay bar. */
209
+ children?: ReactNode
204
210
  }
205
211
 
206
212
  export function TopologyGraph({
@@ -214,13 +220,14 @@ export function TopologyGraph({
214
220
  paused = false,
215
221
  onTogglePause,
216
222
  onMaximizeNamespace,
217
- namespaceBreadcrumb,
218
- onClearNamespace,
219
223
  namespacesKey = '',
220
224
  focusNodeId,
221
225
  focusNonce,
222
226
  focusedOwnerId,
223
227
  onNodeHover,
228
+ onToggleReplicaSets,
229
+ fitViewPadding = 0.15,
230
+ children,
224
231
  }: TopologyGraphProps) {
225
232
  const isTrafficView = viewMode === 'traffic'
226
233
  const [nodes, setNodes, onNodesChangeBase] = useNodesState([] as Node[])
@@ -701,6 +708,7 @@ export function TopologyGraph({
701
708
  onExpand: isExpandablePodGroup ? handleExpandPodGroup : undefined,
702
709
  onCollapse: expandedFromGroup ? handleCollapsePodGroup : undefined,
703
710
  isExpanded: isExpandablePodGroup ? expandedPodGroups.has(node.id) : undefined,
711
+ onToggleReplicaSets: nodeData?.replicaSetsExpandable ? onToggleReplicaSets : undefined,
704
712
  },
705
713
  }
706
714
  })
@@ -736,7 +744,7 @@ export function TopologyGraph({
736
744
  // No cleanup function - we use version-based invalidation instead
737
745
  // This prevents React's effect re-runs from canceling in-flight layouts
738
746
  // when the actual structure hasn't changed
739
- }, [workingNodes, workingEdges, structureKey, groupingMode, hideGroupHeader, collapsedGroups, groupLevels, handleSetLevel, handleCardClick, onMaximizeNamespace, isTrafficView, expandedPodGroups, handleExpandPodGroup, handleCollapsePodGroup, setNodes, setEdges, layoutRetryCount])
747
+ }, [workingNodes, workingEdges, structureKey, groupingMode, hideGroupHeader, collapsedGroups, groupLevels, handleSetLevel, handleCardClick, onMaximizeNamespace, isTrafficView, expandedPodGroups, handleExpandPodGroup, handleCollapsePodGroup, onToggleReplicaSets, setNodes, setEdges, layoutRetryCount])
740
748
 
741
749
  // Handle node click
742
750
  const handleNodeClick = useCallback(
@@ -829,6 +837,55 @@ export function TopologyGraph({
829
837
  })
830
838
  }, [focusedOwnerId, setNodes, nodes])
831
839
 
840
+ // Data-only topology updates must repaint existing nodes without forcing an
841
+ // ELK relayout. Structure hashing deliberately ignores status and metadata;
842
+ // keep the React Flow node payload in sync on that fast path.
843
+ useEffect(() => {
844
+ const topologyById = new Map(workingNodes.map(node => [node.id, node]))
845
+ setNodes(nds => {
846
+ let changed = false
847
+ const updated = nds.map(node => {
848
+ if (node.type === 'group') return node
849
+ const topologyNode = topologyById.get(node.id)
850
+ if (!topologyNode) return node
851
+ const nodeData = topologyNode.data as Record<string, unknown>
852
+ const pods = nodeData.pods
853
+ const isExpandablePodGroup = topologyNode.kind === 'PodGroup' && Array.isArray(pods) && pods.length > 0
854
+ const expandedFromGroup = nodeData.expandedFromGroup as string | undefined
855
+ const onExpand = isExpandablePodGroup ? handleExpandPodGroup : undefined
856
+ const onCollapse = expandedFromGroup ? handleCollapsePodGroup : undefined
857
+ const isExpanded = isExpandablePodGroup ? expandedPodGroups.has(node.id) : undefined
858
+ const toggleReplicaSets = nodeData.replicaSetsExpandable ? onToggleReplicaSets : undefined
859
+ if (
860
+ node.data?.kind === topologyNode.kind &&
861
+ node.data?.name === topologyNode.name &&
862
+ node.data?.status === topologyNode.status &&
863
+ node.data?.nodeData === topologyNode.data &&
864
+ node.data?.onExpand === onExpand &&
865
+ node.data?.onCollapse === onCollapse &&
866
+ node.data?.isExpanded === isExpanded &&
867
+ node.data?.onToggleReplicaSets === toggleReplicaSets
868
+ ) return node
869
+ changed = true
870
+ return {
871
+ ...node,
872
+ data: {
873
+ ...node.data,
874
+ kind: topologyNode.kind,
875
+ name: topologyNode.name,
876
+ status: topologyNode.status,
877
+ nodeData: topologyNode.data,
878
+ onExpand,
879
+ onCollapse,
880
+ isExpanded,
881
+ onToggleReplicaSets: toggleReplicaSets,
882
+ },
883
+ }
884
+ })
885
+ return changed ? updated : nds
886
+ })
887
+ }, [workingNodes, expandedPodGroups, handleExpandPodGroup, handleCollapsePodGroup, onToggleReplicaSets, setNodes])
888
+
832
889
  if (!topology) {
833
890
  return <PaneLoader label="Loading topology…" className="absolute inset-0" />
834
891
  }
@@ -875,86 +932,83 @@ export function TopologyGraph({
875
932
 
876
933
  return (
877
934
  <ReactFlowProvider>
878
- {/* Namespace breadcrumb — shown when viewing a single namespace */}
879
- {namespaceBreadcrumb && (
880
- <div className="absolute top-3 left-3 z-10 flex items-center gap-1.5">
881
- {onClearNamespace && (
882
- <button
883
- onClick={onClearNamespace}
884
- className="text-xs text-theme-text-tertiary hover:text-theme-text-secondary transition-colors"
885
- >
886
- All Namespaces
887
- </button>
888
- )}
889
- {onClearNamespace && (
890
- <span className="text-xs text-theme-text-tertiary">/</span>
891
- )}
892
- <span className="text-xs font-medium text-theme-text-secondary bg-theme-surface/80 backdrop-blur-sm border border-theme-border/50 rounded-md px-2 py-0.5">
893
- {namespaceBreadcrumb}
894
- </span>
895
- </div>
896
- )}
897
- {/* Warning banner for partial topology data */}
898
- {topology?.warnings && topology.warnings.length > 0 && (() => {
899
- const rbacWarnings = topology.warnings.filter(w => w.includes('RBAC not granted'))
900
- const otherWarnings = topology.warnings.filter(w => !w.includes('RBAC not granted'))
901
- const isAllRbac = otherWarnings.length === 0
902
- return (
903
- <div className={`absolute top-2 left-2 right-2 z-10 ${isAllRbac ? 'bg-amber-500/10 border-amber-500/20' : 'bg-amber-500/10 border-amber-500/30'} border rounded-lg p-2 backdrop-blur-sm`}>
935
+ {/* Overlay bar: status banners lead (a partial-data/RBAC warning must read
936
+ before the user trusts the graph), host children stack below. Flex
937
+ column so nothing overlaps. Banners de-absoluted — the bar owns position/z. */}
938
+ <TopologyOverlayBar>
939
+ {/* Warning banner for partial topology data */}
940
+ {topology?.warnings && topology.warnings.length > 0 && (() => {
941
+ const rbacWarnings = topology.warnings.filter(w => w.includes('RBAC not granted'))
942
+ const viewWarnings = topology.warnings.filter(w => w.startsWith('Topology view:'))
943
+ const otherWarnings = topology.warnings.filter(w => !w.includes('RBAC not granted') && !w.startsWith('Topology view:'))
944
+ const isAllRbac = otherWarnings.length === 0 && viewWarnings.length === 0
945
+ const isViewLimited = otherWarnings.length === 0 && rbacWarnings.length === 0 && viewWarnings.length > 0
946
+ return (
947
+ <div className={`pointer-events-auto ${isViewLimited ? 'max-w-xl' : 'w-full'} ${isAllRbac ? 'bg-amber-500/10 border-amber-500/20' : 'bg-amber-500/10 border-amber-500/30'} border rounded-lg p-2 backdrop-blur-sm`}>
948
+ <div className="flex items-start gap-2">
949
+ {isAllRbac ? (
950
+ <Shield className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
951
+ ) : (
952
+ <AlertTriangle className="w-4 h-4 text-amber-500 shrink-0 mt-0.5" />
953
+ )}
954
+ <div className="text-sm">
955
+ <span className="font-medium text-amber-400">
956
+ {isAllRbac ? 'Limited Access:' : isViewLimited ? 'View limited:' : 'Warning:'}
957
+ </span>
958
+ <span className="text-theme-text-secondary ml-1">
959
+ {isAllRbac
960
+ ? `${pluralize(rbacWarnings.length, 'resource type')} not accessible due to RBAC restrictions.`
961
+ : isViewLimited
962
+ ? 'Large retained run history is summarized to keep the graph readable.'
963
+ : 'Some resources failed to load. Data may be incomplete.'}
964
+ </span>
965
+ <details className="mt-1">
966
+ <summary className="text-xs text-amber-400/80 hover:text-amber-400">
967
+ Show details ({topology.warnings.length})
968
+ </summary>
969
+ <ul className="mt-1 text-xs text-theme-text-tertiary space-y-0.5">
970
+ {rbacWarnings.length > 0 && otherWarnings.length > 0 && (
971
+ <li className="text-amber-400/60 font-medium mt-1">RBAC restrictions:</li>
972
+ )}
973
+ {rbacWarnings.map((w, i) => (
974
+ <li key={`rbac-${i}`} className="font-mono">{w}</li>
975
+ ))}
976
+ {viewWarnings.length > 0 && (rbacWarnings.length > 0 || otherWarnings.length > 0) && (
977
+ <li className="text-amber-400/60 font-medium mt-1">View limits:</li>
978
+ )}
979
+ {viewWarnings.map((w, i) => (
980
+ <li key={`view-${i}`} className="font-mono">{w}</li>
981
+ ))}
982
+ {otherWarnings.length > 0 && (rbacWarnings.length > 0 || viewWarnings.length > 0) && (
983
+ <li className="text-amber-400/60 font-medium mt-1">Other warnings:</li>
984
+ )}
985
+ {otherWarnings.map((w, i) => (
986
+ <li key={`other-${i}`} className="font-mono">{w}</li>
987
+ ))}
988
+ </ul>
989
+ </details>
990
+ </div>
991
+ </div>
992
+ </div>
993
+ )
994
+ })()}
995
+ {/* Layout error banner - shown even when stale nodes exist */}
996
+ {layoutError && nodes.length > 0 && (
997
+ <div className="w-full pointer-events-auto bg-red-500/10 border border-red-500/30 rounded-lg p-2 backdrop-blur-sm">
904
998
  <div className="flex items-start gap-2">
905
- {isAllRbac ? (
906
- <Shield className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
907
- ) : (
908
- <AlertTriangle className="w-4 h-4 text-amber-500 shrink-0 mt-0.5" />
909
- )}
999
+ <AlertTriangle className="w-4 h-4 text-red-500 shrink-0 mt-0.5" />
910
1000
  <div className="text-sm">
911
- <span className="font-medium text-amber-400">
912
- {isAllRbac ? 'Limited Access:' : 'Warning:'}
913
- </span>
1001
+ <span className="font-medium text-red-400">Layout Error:</span>
914
1002
  <span className="text-theme-text-secondary ml-1">
915
- {isAllRbac
916
- ? `${pluralize(rbacWarnings.length, 'resource type')} not accessible due to RBAC restrictions.`
917
- : 'Some resources failed to load. Data may be incomplete.'}
1003
+ Failed to update layout. Showing previous view.
918
1004
  </span>
919
- <details className="mt-1">
920
- <summary className="text-xs text-amber-400/80 hover:text-amber-400">
921
- Show details ({topology.warnings.length})
922
- </summary>
923
- <ul className="mt-1 text-xs text-theme-text-tertiary space-y-0.5">
924
- {rbacWarnings.length > 0 && otherWarnings.length > 0 && (
925
- <li className="text-amber-400/60 font-medium mt-1">RBAC restrictions:</li>
926
- )}
927
- {rbacWarnings.map((w, i) => (
928
- <li key={`rbac-${i}`} className="font-mono">{w}</li>
929
- ))}
930
- {otherWarnings.length > 0 && rbacWarnings.length > 0 && (
931
- <li className="text-amber-400/60 font-medium mt-1">Other warnings:</li>
932
- )}
933
- {otherWarnings.map((w, i) => (
934
- <li key={`other-${i}`} className="font-mono">{w}</li>
935
- ))}
936
- </ul>
937
- </details>
1005
+ <p className="mt-1 text-xs text-theme-text-tertiary font-mono">{layoutError}</p>
938
1006
  </div>
939
1007
  </div>
940
1008
  </div>
941
- )
942
- })()}
943
- {/* Layout error banner - shown even when stale nodes exist */}
944
- {layoutError && nodes.length > 0 && (
945
- <div className="absolute top-2 left-2 right-2 z-10 bg-red-500/10 border border-red-500/30 rounded-lg p-2 backdrop-blur-sm">
946
- <div className="flex items-start gap-2">
947
- <AlertTriangle className="w-4 h-4 text-red-500 shrink-0 mt-0.5" />
948
- <div className="text-sm">
949
- <span className="font-medium text-red-400">Layout Error:</span>
950
- <span className="text-theme-text-secondary ml-1">
951
- Failed to update layout. Showing previous view.
952
- </span>
953
- <p className="mt-1 text-xs text-theme-text-tertiary font-mono">{layoutError}</p>
954
- </div>
955
- </div>
956
- </div>
957
- )}
1009
+ )}
1010
+ {children}
1011
+ </TopologyOverlayBar>
958
1012
  {/* Summary-mode pill — pod tier collapsed to per-workload/service counts */}
959
1013
  {topology?.summaryMode && (
960
1014
  <div className="absolute bottom-3 left-1/2 -translate-x-1/2 z-10 flex items-center gap-1.5 bg-blue-500/10 border border-blue-500/30 rounded-full px-3 py-1 backdrop-blur-sm">
@@ -974,7 +1028,7 @@ export function TopologyGraph({
974
1028
  onNodeMouseLeave={handleNodeMouseLeave}
975
1029
  nodeTypes={nodeTypes}
976
1030
  fitView
977
- fitViewOptions={{ padding: 0.2 }}
1031
+ fitViewOptions={{ padding: fitViewPadding }}
978
1032
  minZoom={0.1}
979
1033
  maxZoom={2}
980
1034
  proOptions={{ hideAttribution: true }}
@@ -1021,6 +1075,7 @@ export function TopologyGraph({
1021
1075
  paused={paused}
1022
1076
  onTogglePause={onTogglePause}
1023
1077
  onExportingChange={setIsExporting}
1078
+ fitViewPadding={fitViewPadding}
1024
1079
  />
1025
1080
  </div>
1026
1081
  {!isTrafficView && (
@@ -1057,6 +1112,7 @@ export function TopologyGraph({
1057
1112
  focusNodeId={focusNodeId}
1058
1113
  focusNonce={focusNonce}
1059
1114
  onRequestExpandForNode={expandGroupForNode}
1115
+ fitViewPadding={fitViewPadding}
1060
1116
  />
1061
1117
  </ReactFlow>
1062
1118
  </ReactFlowProvider>
@@ -1240,9 +1296,8 @@ function ExportImageButton({ onExportingChange }: { onExportingChange: (v: boole
1240
1296
  >
1241
1297
  <div className="text-sm font-medium text-theme-text-primary mb-3">Export topology</div>
1242
1298
  <label className="block text-xs text-theme-text-secondary mb-1">Filename</label>
1243
- <input
1299
+ <Input
1244
1300
  ref={inputRef}
1245
- type="text"
1246
1301
  value={filename}
1247
1302
  onChange={(e) => setFilename(e.target.value)}
1248
1303
  className="w-full px-2 py-1.5 text-sm bg-theme-base border border-theme-border rounded text-theme-text-primary outline-none focus:border-blue-500 mb-3"
@@ -1340,11 +1395,13 @@ function CustomControlButtons({
1340
1395
  paused,
1341
1396
  onTogglePause,
1342
1397
  onExportingChange,
1398
+ fitViewPadding,
1343
1399
  }: {
1344
1400
  showExportButton: boolean
1345
1401
  paused: boolean
1346
1402
  onTogglePause?: () => void
1347
1403
  onExportingChange: (v: boolean) => void
1404
+ fitViewPadding: FitViewOptions['padding']
1348
1405
  }) {
1349
1406
  const { zoomIn, zoomOut, fitView } = useReactFlow()
1350
1407
  const TIP = 100
@@ -1361,7 +1418,7 @@ function CustomControlButtons({
1361
1418
  </button>
1362
1419
  </Tooltip>
1363
1420
  <Tooltip content="Fit view" delay={TIP} position="right">
1364
- <button className="react-flow__controls-button" onClick={() => fitView({ padding: 0.15, duration: 400 })}>
1421
+ <button className="react-flow__controls-button" onClick={() => fitView({ padding: fitViewPadding, duration: 400 })}>
1365
1422
  <Maximize className="w-3 h-3" />
1366
1423
  </button>
1367
1424
  </Tooltip>
@@ -1394,6 +1451,7 @@ function ViewportController({
1394
1451
  focusNodeId,
1395
1452
  focusNonce = 0,
1396
1453
  onRequestExpandForNode,
1454
+ fitViewPadding,
1397
1455
  }: {
1398
1456
  viewMode: string
1399
1457
  layoutRetryCount: number
@@ -1403,6 +1461,7 @@ function ViewportController({
1403
1461
  focusNodeId?: string
1404
1462
  focusNonce?: number
1405
1463
  onRequestExpandForNode?: (nodeId: string) => void
1464
+ fitViewPadding: FitViewOptions['padding']
1406
1465
  }) {
1407
1466
  const { fitView, zoomIn, zoomOut, setViewport, getViewport, getInternalNode, setCenter } = useReactFlow()
1408
1467
  const nodes = useNodes() // Reactive hook to watch node changes
@@ -1431,7 +1490,7 @@ function ViewportController({
1431
1490
  description: 'Fit graph to screen',
1432
1491
  category: 'Topology',
1433
1492
  scope: 'topology',
1434
- handler: () => fitView({ padding: 0.15, duration: VIEWPORT_ANIMATION_DURATION }),
1493
+ handler: () => fitView({ padding: fitViewPadding, duration: VIEWPORT_ANIMATION_DURATION }),
1435
1494
  },
1436
1495
  {
1437
1496
  id: 'topology-zoom-in',
@@ -1508,14 +1567,14 @@ function ViewportController({
1508
1567
  if (nodesJustPopulated || viewModeChanged || retryRequested || fitViewRequested) {
1509
1568
  const timeoutId = setTimeout(() => {
1510
1569
  fitView({
1511
- padding: 0.15,
1570
+ padding: fitViewPadding,
1512
1571
  duration: nodesJustPopulated ? 0 : VIEWPORT_ANIMATION_DURATION,
1513
1572
  })
1514
1573
  }, 10)
1515
1574
 
1516
1575
  return () => clearTimeout(timeoutId)
1517
1576
  }
1518
- }, [viewMode, layoutRetryCount, fitViewCounter, nodes.length, fitView])
1577
+ }, [viewMode, layoutRetryCount, fitViewCounter, nodes.length, fitView, fitViewPadding])
1519
1578
 
1520
1579
  // Pan/zoom to a single searched node. Gated on focusNonce so the same
1521
1580
  // node can be re-focused, and so this never fires on background updates.
@@ -1563,10 +1622,10 @@ function ViewportController({
1563
1622
  if (!fitAllAfterLayoutRef?.current) return
1564
1623
  const id = setTimeout(() => {
1565
1624
  fitAllAfterLayoutRef.current = false
1566
- fitView({ padding: 0.15, duration: VIEWPORT_ANIMATION_DURATION })
1625
+ fitView({ padding: fitViewPadding, duration: VIEWPORT_ANIMATION_DURATION })
1567
1626
  }, 250)
1568
1627
  return () => clearTimeout(id)
1569
- }, [nodes, fitView, fitAllAfterLayoutRef])
1628
+ }, [nodes, fitView, fitAllAfterLayoutRef, fitViewPadding])
1570
1629
 
1571
1630
  return null
1572
1631
  }
@@ -0,0 +1,30 @@
1
+ import type { ReactNode } from 'react'
2
+ import { clsx } from 'clsx'
3
+
4
+ interface TopologyOverlayBarProps {
5
+ children: ReactNode
6
+ /** Extra classes merged onto the bar container. */
7
+ className?: string
8
+ }
9
+
10
+ /**
11
+ * Top-anchored overlay bar for the topology canvas. Stacks children top-to-
12
+ * bottom (flex column, source order); wrap items in your own `flex` div to put
13
+ * them on one line. Everything stacks, so overlays never collide.
14
+ *
15
+ * `pointer-events-none` so the canvas stays pannable in the gaps — interactive
16
+ * children opt back in with `pointer-events-auto`. Children are `items-start`
17
+ * (content width); use `w-full` for a full-width row.
18
+ */
19
+ export function TopologyOverlayBar({ children, className }: TopologyOverlayBarProps) {
20
+ return (
21
+ <div
22
+ className={clsx(
23
+ 'pointer-events-none absolute inset-x-2 top-2 z-10 flex flex-col items-start gap-2',
24
+ className,
25
+ )}
26
+ >
27
+ {children}
28
+ </div>
29
+ )
30
+ }
@@ -1,5 +1,7 @@
1
1
  import { useState, useCallback, useEffect, useRef, useMemo } from 'react'
2
+ import { createPortal } from 'react-dom'
2
3
  import { Search, X, ChevronRight } from 'lucide-react'
4
+ import { Input } from '../ui/Input'
3
5
  import { getTopologyIcon } from '../../utils/resource-icons'
4
6
  import { clsx } from 'clsx'
5
7
  import type { TopologyNode } from '../../types'
@@ -23,12 +25,12 @@ interface TopologySearchProps {
23
25
  * empty-state hint above. Defaults to "current view".
24
26
  */
25
27
  viewModeLabel?: string
26
- /**
27
- * Position utilities for the trigger button (default "top-4 left-4").
28
- * The host overrides this to avoid colliding with other canvas overlays
29
- * (e.g. stacking below the namespace breadcrumb).
30
- */
31
- triggerClassName?: string
28
+ /** Element the search overlay portals into — typically the topology pane. Set
29
+ * it when the trigger lives inside a `pointer-events-none` overlay bar: the
30
+ * overlay then renders OUTSIDE that bar (so its backdrop is clickable) and is
31
+ * `absolute inset-0` scoped to this container (dims only the pane). Omit to
32
+ * fall back to a viewport-level `fixed` overlay on document.body. */
33
+ overlayContainer?: HTMLElement | null
32
34
  }
33
35
 
34
36
  // Icon mapping for different resource kinds
@@ -70,7 +72,7 @@ function getKindColor(kind: string): string {
70
72
  }
71
73
  }
72
74
 
73
- export function TopologySearch({ nodes, onNodeSelect, onZoomToNode, allNodes, viewModeLabel, triggerClassName }: TopologySearchProps) {
75
+ export function TopologySearch({ nodes, onNodeSelect, onZoomToNode, allNodes, viewModeLabel, overlayContainer }: TopologySearchProps) {
74
76
  const [isOpen, setIsOpen] = useState(false)
75
77
  const [query, setQuery] = useState('')
76
78
  const [selectedIndex, setSelectedIndex] = useState(0)
@@ -194,15 +196,20 @@ export function TopologySearch({ nodes, onNodeSelect, onZoomToNode, allNodes, vi
194
196
  setTimeout(() => inputRef.current?.focus(), 0)
195
197
  }, [])
196
198
 
199
+ // Portal the overlay OUT of the (pointer-events-none) overlay bar the trigger
200
+ // sits in. With a container → `absolute inset-0` scoped to it (the pane);
201
+ // without → a viewport `fixed` overlay on document.body.
202
+ const portalTarget = overlayContainer ?? (typeof document !== 'undefined' ? document.body : null)
203
+ const overlayPositionClass = overlayContainer ? 'absolute' : 'fixed'
204
+
197
205
  return (
198
206
  <>
199
207
  {/* Search trigger button */}
200
208
  <button
201
209
  onClick={handleOpen}
202
- className={clsx(
203
- 'absolute z-10 flex items-center gap-2 px-3 py-2 bg-theme-surface/90 backdrop-blur border border-theme-border rounded-lg text-theme-text-secondary hover:text-theme-text-primary hover:border-theme-border-light transition-colors',
204
- triggerClassName ?? 'top-4 left-4'
205
- )}
210
+ // Layout-neutral (positioned by the host's overlay bar); pointer-events-
211
+ // auto inside the pointer-events-none bar.
212
+ className="pointer-events-auto flex items-center gap-2 px-3 py-2 bg-theme-surface/90 backdrop-blur border border-theme-border rounded-lg text-theme-text-secondary hover:text-theme-text-primary hover:border-theme-border-light transition-colors"
206
213
  >
207
214
  <Search className="w-4 h-4" />
208
215
  <span className="text-sm">Search</span>
@@ -211,9 +218,10 @@ export function TopologySearch({ nodes, onNodeSelect, onZoomToNode, allNodes, vi
211
218
  </kbd>
212
219
  </button>
213
220
 
214
- {/* Search modal */}
215
- {isOpen && (
216
- <div className="absolute inset-0 z-50 flex items-start justify-center pt-[10vh]">
221
+ {/* Portaled out of the overlay bar (see portalTarget above) so the
222
+ backdrop is clickable and the dim covers only the pane, not the app. */}
223
+ {isOpen && portalTarget && createPortal(
224
+ <div className={`${overlayPositionClass} inset-0 z-50 flex items-start justify-center pt-[10vh]`}>
217
225
  {/* Backdrop */}
218
226
  <div
219
227
  className="absolute inset-0 bg-theme-base/60 backdrop-blur-sm"
@@ -225,9 +233,8 @@ export function TopologySearch({ nodes, onNodeSelect, onZoomToNode, allNodes, vi
225
233
  {/* Search input */}
226
234
  <div className="flex items-center gap-3 px-4 py-3 border-b border-theme-border">
227
235
  <Search className="w-5 h-5 text-theme-text-secondary" />
228
- <input
236
+ <Input
229
237
  ref={inputRef}
230
- type="text"
231
238
  value={query}
232
239
  onChange={(e) => setQuery(e.target.value)}
233
240
  onKeyDown={handleInputKeyDown}
@@ -339,7 +346,8 @@ export function TopologySearch({ nodes, onNodeSelect, onZoomToNode, allNodes, vi
339
346
  </div>
340
347
  )}
341
348
  </div>
342
- </div>
349
+ </div>,
350
+ portalTarget,
343
351
  )}
344
352
  </>
345
353
  )
@@ -4,4 +4,6 @@ export { GroupNode } from './GroupNode'
4
4
  export { TopologySearch } from './TopologySearch'
5
5
  export { TopologyFilterSidebar } from './TopologyFilterSidebar'
6
6
  export { TopologyControls } from './TopologyControls'
7
+ export { TopologyOverlayBar } from './TopologyOverlayBar'
8
+ export { TopologyBreadcrumb } from './TopologyBreadcrumb'
7
9
  export { buildHierarchicalElkGraph, applyHierarchicalLayout, getGroupKey, setLayoutEngine } from './layout'
@@ -827,8 +827,16 @@ function computeWorkloadCards(
827
827
  subtitle = (d.type as string) || 'ClusterIP'
828
828
  } else if (primary.kind === 'CronJob') {
829
829
  subtitle = (d.schedule as string) || ''
830
+ } else if (primary.kind === 'CronWorkflow') {
831
+ subtitle = (d.schedule as string) || ''
830
832
  } else if (primary.kind === 'Job') {
831
833
  subtitle = (d.phase as string) || ''
834
+ } else if (primary.kind === 'Workflow') {
835
+ subtitle = (d.progress as string) || (d.phase as string) || ''
836
+ } else if (primary.kind === 'WorkflowTemplate' || primary.kind === 'ClusterWorkflowTemplate') {
837
+ const entrypoint = d.entrypoint as string
838
+ const templateCount = d.templateCount as number
839
+ subtitle = entrypoint || (templateCount ? `${templateCount} templates` : '')
832
840
  } else if (primary.kind === 'Application') {
833
841
  const sync = d.syncStatus as string
834
842
  const health = d.healthStatus as string
@@ -855,10 +863,10 @@ function computeWorkloadCards(
855
863
  // Shared kind priority map — used by both pickGroupName and computeWorkloadCards
856
864
  const KIND_PRIORITY: Record<string, number> = {
857
865
  'Deployment': 1, 'Rollout': 1, 'StatefulSet': 2, 'DaemonSet': 3,
858
- 'CronJob': 4, 'Job': 5, 'Service': 6, 'Gateway': 7,
866
+ 'CronJob': 4, 'CronWorkflow': 4, 'Job': 5, 'Workflow': 5, 'Service': 6, 'Gateway': 7,
859
867
  'HTTPRoute': 6, 'GRPCRoute': 6, 'TCPRoute': 6, 'TLSRoute': 6, 'Ingress': 7,
860
868
  'ReplicaSet': 8, 'Pod': 9, 'PodGroup': 9,
861
- 'ConfigMap': 10, 'Secret': 10, 'PersistentVolumeClaim': 10, 'HorizontalPodAutoscaler': 10,
869
+ 'ConfigMap': 10, 'Secret': 10, 'ServiceAccount': 10, 'SealedSecret': 10, 'ServiceMonitor': 10, 'PodMonitor': 10, 'WorkflowTemplate': 10, 'ClusterWorkflowTemplate': 10, 'PersistentVolumeClaim': 10, 'HorizontalPodAutoscaler': 10,
862
870
  'KnativeService': 1, 'KnativeConfiguration': 3, 'KnativeRevision': 4, 'KnativeRoute': 2,
863
871
  'Broker': 2, 'Channel': 2, 'Trigger': 3, 'PingSource': 3, 'ApiServerSource': 3,
864
872
  'ContainerSource': 3, 'SinkBinding': 3,