@skyhook-io/k8s-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/package.json +63 -0
  2. package/src/components/dock/BottomDock.tsx +204 -0
  3. package/src/components/dock/DockContext.tsx +212 -0
  4. package/src/components/dock/TerminalTab.tsx +333 -0
  5. package/src/components/dock/index.ts +3 -0
  6. package/src/components/gitops/GitOpsActions.tsx +227 -0
  7. package/src/components/gitops/GitOpsStatusBadge.tsx +221 -0
  8. package/src/components/gitops/ManagedResourcesList.tsx +210 -0
  9. package/src/components/gitops/SyncCountdown.tsx +146 -0
  10. package/src/components/gitops/index.ts +4 -0
  11. package/src/components/logs/JsonLogLine.tsx +90 -0
  12. package/src/components/logs/LogCore.tsx +553 -0
  13. package/src/components/logs/LogToolbarSelects.tsx +74 -0
  14. package/src/components/logs/LogsViewer.tsx +146 -0
  15. package/src/components/logs/WorkloadLogsViewer.tsx +263 -0
  16. package/src/components/logs/index.ts +13 -0
  17. package/src/components/logs/useLogBuffer.ts +138 -0
  18. package/src/components/logs/useLogSearch.ts +151 -0
  19. package/src/components/logs/useLogStream.ts +61 -0
  20. package/src/components/resources/ResourcesView.tsx +5494 -0
  21. package/src/components/resources/index.ts +17 -0
  22. package/src/components/resources/renderers/AlertRenderer.tsx +166 -0
  23. package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +233 -0
  24. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +89 -0
  25. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +194 -0
  26. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +79 -0
  27. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +68 -0
  28. package/src/components/resources/renderers/CertificateRenderer.tsx +209 -0
  29. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +123 -0
  30. package/src/components/resources/renderers/ChallengeRenderer.tsx +111 -0
  31. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +164 -0
  32. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +140 -0
  33. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +153 -0
  34. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +221 -0
  35. package/src/components/resources/renderers/ConfigMapRenderer.tsx +124 -0
  36. package/src/components/resources/renderers/CronJobRenderer.tsx +86 -0
  37. package/src/components/resources/renderers/EventRenderer.tsx +175 -0
  38. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +119 -0
  39. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +172 -0
  40. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +242 -0
  41. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +263 -0
  42. package/src/components/resources/renderers/GatewayClassRenderer.tsx +68 -0
  43. package/src/components/resources/renderers/GatewayRenderer.tsx +218 -0
  44. package/src/components/resources/renderers/GenericRenderer.tsx +476 -0
  45. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +154 -0
  46. package/src/components/resources/renderers/HPARenderer.tsx +164 -0
  47. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +303 -0
  48. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +167 -0
  49. package/src/components/resources/renderers/IngressClassRenderer.tsx +49 -0
  50. package/src/components/resources/renderers/IngressRenderer.tsx +104 -0
  51. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +175 -0
  52. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +176 -0
  53. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +125 -0
  54. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +84 -0
  55. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +112 -0
  56. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +288 -0
  57. package/src/components/resources/renderers/JobRenderer.tsx +103 -0
  58. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +157 -0
  59. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +188 -0
  60. package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +148 -0
  61. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +90 -0
  62. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +136 -0
  63. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +151 -0
  64. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +70 -0
  65. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +309 -0
  66. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +142 -0
  67. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +232 -0
  68. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +97 -0
  69. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +74 -0
  70. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +138 -0
  71. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +220 -0
  72. package/src/components/resources/renderers/KustomizationRenderer.tsx +151 -0
  73. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +352 -0
  74. package/src/components/resources/renderers/LeaseRenderer.tsx +93 -0
  75. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +208 -0
  76. package/src/components/resources/renderers/NodeRenderer.tsx +282 -0
  77. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +153 -0
  78. package/src/components/resources/renderers/OrderRenderer.tsx +108 -0
  79. package/src/components/resources/renderers/PVCRenderer.tsx +98 -0
  80. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +138 -0
  81. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +126 -0
  82. package/src/components/resources/renderers/PodMonitorRenderer.tsx +63 -0
  83. package/src/components/resources/renderers/PodRenderer.tsx +677 -0
  84. package/src/components/resources/renderers/PriorityClassRenderer.tsx +50 -0
  85. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +62 -0
  86. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +71 -0
  87. package/src/components/resources/renderers/RoleBindingRenderer.tsx +97 -0
  88. package/src/components/resources/renderers/RoleRenderer.tsx +192 -0
  89. package/src/components/resources/renderers/RolloutRenderer.tsx +227 -0
  90. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +75 -0
  91. package/src/components/resources/renderers/SbomReportRenderer.tsx +98 -0
  92. package/src/components/resources/renderers/SealedSecretRenderer.tsx +121 -0
  93. package/src/components/resources/renderers/SecretRenderer.tsx +321 -0
  94. package/src/components/resources/renderers/SecretStoreRenderer.tsx +111 -0
  95. package/src/components/resources/renderers/ServiceAccountRenderer.tsx +81 -0
  96. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +65 -0
  97. package/src/components/resources/renderers/ServiceRenderer.tsx +98 -0
  98. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +195 -0
  99. package/src/components/resources/renderers/StorageClassRenderer.tsx +82 -0
  100. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +257 -0
  101. package/src/components/resources/renderers/VPARenderer.tsx +193 -0
  102. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +73 -0
  103. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +193 -0
  104. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +161 -0
  105. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +134 -0
  106. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +36 -0
  107. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +347 -0
  108. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +156 -0
  109. package/src/components/resources/renderers/WorkflowRenderer.tsx +202 -0
  110. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +91 -0
  111. package/src/components/resources/renderers/WorkloadRenderer.tsx +311 -0
  112. package/src/components/resources/renderers/argo-cells.tsx +118 -0
  113. package/src/components/resources/renderers/certmanager-cells.tsx +164 -0
  114. package/src/components/resources/renderers/cnpg-cells.tsx +161 -0
  115. package/src/components/resources/renderers/eso-cells.tsx +115 -0
  116. package/src/components/resources/renderers/flux-cells.tsx +253 -0
  117. package/src/components/resources/renderers/index.ts +105 -0
  118. package/src/components/resources/renderers/istio-cells.tsx +210 -0
  119. package/src/components/resources/renderers/karpenter-cells.tsx +107 -0
  120. package/src/components/resources/renderers/keda-cells.tsx +94 -0
  121. package/src/components/resources/renderers/knative-cells.tsx +396 -0
  122. package/src/components/resources/renderers/kyverno-cells.tsx +85 -0
  123. package/src/components/resources/renderers/prometheus-cells.tsx +74 -0
  124. package/src/components/resources/renderers/traefik-cells.tsx +120 -0
  125. package/src/components/resources/renderers/trivy-cells.tsx +153 -0
  126. package/src/components/resources/renderers/trivy-shared.tsx +67 -0
  127. package/src/components/resources/renderers/velero-cells.tsx +164 -0
  128. package/src/components/resources/resource-utils-argo.ts +155 -0
  129. package/src/components/resources/resource-utils-certmanager.ts +198 -0
  130. package/src/components/resources/resource-utils-cnpg.ts +344 -0
  131. package/src/components/resources/resource-utils-eso.ts +385 -0
  132. package/src/components/resources/resource-utils-flux.ts +186 -0
  133. package/src/components/resources/resource-utils-istio.ts +372 -0
  134. package/src/components/resources/resource-utils-karpenter.ts +150 -0
  135. package/src/components/resources/resource-utils-keda.ts +203 -0
  136. package/src/components/resources/resource-utils-knative.ts +218 -0
  137. package/src/components/resources/resource-utils-kyverno.ts +224 -0
  138. package/src/components/resources/resource-utils-prometheus.ts +159 -0
  139. package/src/components/resources/resource-utils-traefik.ts +180 -0
  140. package/src/components/resources/resource-utils-trivy.ts +146 -0
  141. package/src/components/resources/resource-utils-velero.ts +293 -0
  142. package/src/components/resources/resource-utils.ts +1558 -0
  143. package/src/components/shared/EditableYamlView.tsx +302 -0
  144. package/src/components/shared/ResourceActionsBar.tsx +924 -0
  145. package/src/components/shared/ResourceRendererDispatch.tsx +557 -0
  146. package/src/components/shared/index.ts +3 -0
  147. package/src/components/timeline/index.ts +1 -0
  148. package/src/components/timeline/shared.tsx +614 -0
  149. package/src/components/topology/GroupNode.tsx +198 -0
  150. package/src/components/topology/K8sResourceNode.tsx +440 -0
  151. package/src/components/topology/TopologyControls.tsx +62 -0
  152. package/src/components/topology/TopologyFilterSidebar.tsx +377 -0
  153. package/src/components/topology/TopologyGraph.tsx +748 -0
  154. package/src/components/topology/TopologySearch.tsx +304 -0
  155. package/src/components/topology/index.ts +7 -0
  156. package/src/components/topology/layout.ts +734 -0
  157. package/src/components/topology/layout.worker.ts +272 -0
  158. package/src/components/topology/topology.css +129 -0
  159. package/src/components/ui/CodeViewer.tsx +451 -0
  160. package/src/components/ui/ConfirmDialog.tsx +188 -0
  161. package/src/components/ui/ForceDeleteConfirmDialog.tsx +57 -0
  162. package/src/components/ui/HealthRing.tsx +72 -0
  163. package/src/components/ui/MetricsChart.tsx +259 -0
  164. package/src/components/ui/ResourceBar.tsx +68 -0
  165. package/src/components/ui/Toast.tsx +225 -0
  166. package/src/components/ui/Tooltip.tsx +165 -0
  167. package/src/components/ui/YamlEditor.tsx +280 -0
  168. package/src/components/ui/drawer-components.tsx +792 -0
  169. package/src/components/ui/index.ts +10 -0
  170. package/src/components/workload/ResourceDetailDrawer.tsx +158 -0
  171. package/src/components/workload/WorkloadView.tsx +1109 -0
  172. package/src/components/workload/index.ts +2 -0
  173. package/src/hooks/index.ts +9 -0
  174. package/src/hooks/useAnimatedUnmount.ts +30 -0
  175. package/src/hooks/useKeyboardShortcuts.tsx +336 -0
  176. package/src/hooks/useRefreshAnimation.ts +58 -0
  177. package/src/index.ts +35 -0
  178. package/src/types/core.ts +894 -0
  179. package/src/types/gitops.ts +458 -0
  180. package/src/types/index.ts +2 -0
  181. package/src/utils/animation.ts +40 -0
  182. package/src/utils/api-resources.ts +185 -0
  183. package/src/utils/badge-colors.ts +399 -0
  184. package/src/utils/context-name.ts +72 -0
  185. package/src/utils/download.ts +19 -0
  186. package/src/utils/format.ts +233 -0
  187. package/src/utils/index.ts +10 -0
  188. package/src/utils/log-format.ts +300 -0
  189. package/src/utils/navigation.ts +119 -0
  190. package/src/utils/resource-hierarchy.ts +630 -0
  191. package/src/utils/resource-icons.ts +206 -0
  192. package/src/vite-env.d.ts +1 -0
  193. package/tsconfig.json +24 -0
@@ -0,0 +1,377 @@
1
+ import { memo, useMemo, useState } from 'react'
2
+ import {
3
+ ChevronLeft,
4
+ ChevronRight,
5
+ Eye,
6
+ EyeOff,
7
+ AlertTriangle,
8
+ Zap
9
+ } from 'lucide-react'
10
+ import type { LucideIcon } from 'lucide-react'
11
+ import { clsx } from 'clsx'
12
+ import type { NodeKind, TopologyNode } from '../../types'
13
+ import { getTopologyIcon } from '../../utils/resource-icons'
14
+
15
+ // Resource kind configuration
16
+ const RESOURCE_KINDS: {
17
+ kind: NodeKind
18
+ label: string
19
+ icon: LucideIcon
20
+ color: string
21
+ category: 'gitops' | 'workloads' | 'networking' | 'config' | 'scaling' | 'custom'
22
+ }[] = [
23
+ // GitOps (ArgoCD + FluxCD)
24
+ { kind: 'Application', label: 'Application', icon: getTopologyIcon('Application'), color: 'text-orange-400', category: 'gitops' },
25
+ { kind: 'Kustomization', label: 'Kustomization', icon: getTopologyIcon('Kustomization'), color: 'text-sky-400', category: 'gitops' },
26
+ { kind: 'HelmRelease', label: 'HelmRelease', icon: getTopologyIcon('HelmRelease'), color: 'text-sky-400', category: 'gitops' },
27
+ { kind: 'GitRepository', label: 'GitRepository', icon: getTopologyIcon('GitRepository'), color: 'text-teal-400', category: 'gitops' },
28
+
29
+ // Networking
30
+ { kind: 'Ingress', label: 'Ingress', icon: getTopologyIcon('Ingress'), color: 'text-purple-400', category: 'networking' },
31
+ { kind: 'Gateway', label: 'Gateway', icon: getTopologyIcon('Gateway'), color: 'text-purple-400', category: 'networking' },
32
+ { kind: 'HTTPRoute', label: 'HTTPRoute', icon: getTopologyIcon('HTTPRoute'), color: 'text-purple-300', category: 'networking' },
33
+ { kind: 'GRPCRoute', label: 'GRPCRoute', icon: getTopologyIcon('GRPCRoute'), color: 'text-purple-300', category: 'networking' },
34
+ { kind: 'TCPRoute', label: 'TCPRoute', icon: getTopologyIcon('TCPRoute'), color: 'text-purple-300', category: 'networking' },
35
+ { kind: 'TLSRoute', label: 'TLSRoute', icon: getTopologyIcon('TLSRoute'), color: 'text-purple-300', category: 'networking' },
36
+ { kind: 'Service', label: 'Service', icon: getTopologyIcon('Service'), color: 'text-blue-400', category: 'networking' },
37
+
38
+ // Workloads
39
+ { kind: 'Deployment', label: 'Deployment', icon: getTopologyIcon('Deployment'), color: 'text-emerald-400', category: 'workloads' },
40
+ { kind: 'Rollout', label: 'Rollout', icon: getTopologyIcon('Rollout'), color: 'text-emerald-400', category: 'workloads' },
41
+ { kind: 'DaemonSet', label: 'DaemonSet', icon: getTopologyIcon('DaemonSet'), color: 'text-teal-400', category: 'workloads' },
42
+ { kind: 'StatefulSet', label: 'StatefulSet', icon: getTopologyIcon('StatefulSet'), color: 'text-cyan-400', category: 'workloads' },
43
+ { kind: 'ReplicaSet', label: 'ReplicaSet', icon: getTopologyIcon('ReplicaSet'), color: 'text-green-400', category: 'workloads' },
44
+ { kind: 'Pod', label: 'Pod', icon: getTopologyIcon('Pod'), color: 'text-lime-400', category: 'workloads' },
45
+ { kind: 'PodGroup', label: 'Pod Group', icon: getTopologyIcon('PodGroup'), color: 'text-lime-400', category: 'workloads' },
46
+ { kind: 'Job', label: 'Job', icon: getTopologyIcon('Job'), color: 'text-orange-400', category: 'workloads' },
47
+ { kind: 'CronJob', label: 'CronJob', icon: getTopologyIcon('CronJob'), color: 'text-orange-300', category: 'workloads' },
48
+
49
+ // Config
50
+ { kind: 'ConfigMap', label: 'ConfigMap', icon: getTopologyIcon('ConfigMap'), color: 'text-amber-400', category: 'config' },
51
+ { kind: 'Secret', label: 'Secret', icon: getTopologyIcon('Secret'), color: 'text-red-400', category: 'config' },
52
+
53
+ // Scaling
54
+ { kind: 'HorizontalPodAutoscaler', label: 'HPA', icon: getTopologyIcon('HorizontalPodAutoscaler'), color: 'text-pink-400', category: 'scaling' },
55
+
56
+ // Knative
57
+ { kind: 'KnativeService', label: 'Knative Service', icon: getTopologyIcon('KnativeService'), color: 'text-fuchsia-400', category: 'custom' },
58
+ { kind: 'KnativeConfiguration', label: 'Knative Config', icon: getTopologyIcon('KnativeConfiguration'), color: 'text-fuchsia-400', category: 'custom' },
59
+ { kind: 'KnativeRevision', label: 'Revision', icon: getTopologyIcon('KnativeRevision'), color: 'text-fuchsia-400', category: 'custom' },
60
+ { kind: 'KnativeRoute', label: 'Route', icon: getTopologyIcon('KnativeRoute'), color: 'text-fuchsia-400', category: 'custom' },
61
+ { kind: 'Broker', label: 'Broker', icon: getTopologyIcon('Broker'), color: 'text-fuchsia-400', category: 'custom' },
62
+ { kind: 'Channel', label: 'Channel', icon: getTopologyIcon('Channel'), color: 'text-fuchsia-400', category: 'custom' },
63
+ { kind: 'Trigger', label: 'Trigger', icon: getTopologyIcon('Trigger'), color: 'text-fuchsia-400', category: 'custom' },
64
+ { kind: 'PingSource', label: 'PingSource', icon: getTopologyIcon('PingSource'), color: 'text-fuchsia-400', category: 'custom' },
65
+ { kind: 'ApiServerSource', label: 'ApiServerSource', icon: getTopologyIcon('ApiServerSource'), color: 'text-fuchsia-400', category: 'custom' },
66
+ { kind: 'ContainerSource', label: 'ContainerSource', icon: getTopologyIcon('ContainerSource'), color: 'text-fuchsia-400', category: 'custom' },
67
+ { kind: 'SinkBinding', label: 'SinkBinding', icon: getTopologyIcon('SinkBinding'), color: 'text-fuchsia-400', category: 'custom' },
68
+
69
+ // Traefik
70
+ { kind: 'IngressRoute', label: 'IngressRoute', icon: getTopologyIcon('IngressRoute'), color: 'text-cyan-400', category: 'networking' },
71
+ { kind: 'IngressRouteTCP', label: 'TCP Route', icon: getTopologyIcon('IngressRouteTCP'), color: 'text-cyan-400', category: 'networking' },
72
+ { kind: 'IngressRouteUDP', label: 'UDP Route', icon: getTopologyIcon('IngressRouteUDP'), color: 'text-cyan-400', category: 'networking' },
73
+ { kind: 'Middleware', label: 'Middleware', icon: getTopologyIcon('Middleware'), color: 'text-cyan-400', category: 'custom' },
74
+ { kind: 'MiddlewareTCP', label: 'MW TCP', icon: getTopologyIcon('MiddlewareTCP'), color: 'text-cyan-400', category: 'custom' },
75
+ { kind: 'TraefikService', label: 'Traefik Svc', icon: getTopologyIcon('TraefikService'), color: 'text-cyan-400', category: 'custom' },
76
+ { kind: 'ServersTransport', label: 'Transport', icon: getTopologyIcon('ServersTransport'), color: 'text-cyan-400', category: 'custom' },
77
+ { kind: 'ServersTransportTCP', label: 'Transport TCP', icon: getTopologyIcon('ServersTransportTCP'), color: 'text-cyan-400', category: 'custom' },
78
+ { kind: 'TLSOption', label: 'TLS Option', icon: getTopologyIcon('TLSOption'), color: 'text-cyan-400', category: 'custom' },
79
+ { kind: 'TLSStore', label: 'TLS Store', icon: getTopologyIcon('TLSStore'), color: 'text-cyan-400', category: 'custom' },
80
+ ]
81
+
82
+ const CATEGORIES = [
83
+ { id: 'gitops', label: 'GitOps' },
84
+ { id: 'networking', label: 'Networking' },
85
+ { id: 'workloads', label: 'Workloads' },
86
+ { id: 'config', label: 'Configuration' },
87
+ { id: 'scaling', label: 'Scaling' },
88
+ { id: 'custom', label: 'Custom Resources' },
89
+ ] as const
90
+
91
+ interface TopologyFilterSidebarProps {
92
+ nodes: TopologyNode[]
93
+ visibleKinds: Set<NodeKind>
94
+ onToggleKind: (kind: NodeKind) => void
95
+ onShowAll: () => void
96
+ onHideAll: () => void
97
+ collapsed?: boolean
98
+ onToggleCollapse?: () => void
99
+ hiddenKinds?: string[] // Kinds auto-hidden for performance in large clusters
100
+ onEnableHiddenKind?: (kind: string) => void // Callback to re-enable a hidden kind
101
+ }
102
+
103
+ export const TopologyFilterSidebar = memo(function TopologyFilterSidebar({
104
+ nodes,
105
+ visibleKinds,
106
+ onToggleKind,
107
+ onShowAll,
108
+ onHideAll,
109
+ collapsed = false,
110
+ onToggleCollapse,
111
+ hiddenKinds = [],
112
+ onEnableHiddenKind,
113
+ }: TopologyFilterSidebarProps) {
114
+ // Track which hidden kinds the user has confirmed to show (performance warning acknowledged)
115
+ const [confirmedKinds, setConfirmedKinds] = useState<Set<string>>(new Set())
116
+ // Track which kind is pending confirmation
117
+ const [pendingConfirmKind, setPendingConfirmKind] = useState<string | null>(null)
118
+ // Count nodes by kind
119
+ const kindCounts = useMemo(() => {
120
+ const counts = new Map<NodeKind, number>()
121
+ for (const node of nodes) {
122
+ counts.set(node.kind, (counts.get(node.kind) || 0) + 1)
123
+ }
124
+ return counts
125
+ }, [nodes])
126
+
127
+ // Filter to only show kinds that exist in the topology
128
+ // Also include dynamic CRD kinds not in RESOURCE_KINDS
129
+ const availableKinds = useMemo(() => {
130
+ // Start with known kinds that exist
131
+ const known = RESOURCE_KINDS.filter(k => kindCounts.has(k.kind))
132
+
133
+ // Find CRD kinds not in RESOURCE_KINDS (dynamic CRDs)
134
+ const knownKindSet = new Set(RESOURCE_KINDS.map(k => k.kind))
135
+ const dynamicKinds: typeof RESOURCE_KINDS = []
136
+
137
+ for (const [kind] of kindCounts) {
138
+ if (!knownKindSet.has(kind) && kind !== 'Internet') {
139
+ dynamicKinds.push({
140
+ kind: kind as NodeKind,
141
+ label: kind, // Use the kind name as label
142
+ icon: getTopologyIcon(kind), // Returns Puzzle for unknown kinds
143
+ color: 'text-gray-400',
144
+ category: 'custom',
145
+ })
146
+ }
147
+ }
148
+
149
+ return [...known, ...dynamicKinds]
150
+ }, [kindCounts])
151
+
152
+ // Group by category
153
+ const kindsByCategory = useMemo(() => {
154
+ const grouped = new Map<string, typeof availableKinds>()
155
+ for (const category of CATEGORIES) {
156
+ const kinds = availableKinds.filter(k => k.category === category.id)
157
+ if (kinds.length > 0) {
158
+ grouped.set(category.id, kinds)
159
+ }
160
+ }
161
+ return grouped
162
+ }, [availableKinds])
163
+
164
+ const allVisible = availableKinds.every(k => visibleKinds.has(k.kind))
165
+ const noneVisible = availableKinds.every(k => !visibleKinds.has(k.kind))
166
+
167
+ if (collapsed) {
168
+ return (
169
+ <div className="flex flex-col items-center py-3 px-1 bg-theme-surface/90 backdrop-blur border-r border-theme-border">
170
+ <button
171
+ onClick={onToggleCollapse}
172
+ className="p-2 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded-lg transition-colors"
173
+ title="Expand filters"
174
+ >
175
+ <ChevronRight className="w-4 h-4" />
176
+ </button>
177
+ <div className="mt-3 flex flex-col gap-2">
178
+ {availableKinds.slice(0, 6).map(({ kind, icon: Icon, color }) => {
179
+ const isVisible = visibleKinds.has(kind)
180
+ return (
181
+ <button
182
+ key={kind}
183
+ onClick={() => onToggleKind(kind)}
184
+ className={clsx(
185
+ 'p-1.5 rounded transition-colors',
186
+ isVisible
187
+ ? 'bg-theme-elevated text-theme-text-primary'
188
+ : 'text-theme-text-tertiary hover:text-theme-text-secondary'
189
+ )}
190
+ title={kind}
191
+ >
192
+ <Icon className={clsx('w-4 h-4', isVisible && color)} />
193
+ </button>
194
+ )
195
+ })}
196
+ {availableKinds.length > 6 && (
197
+ <span className="text-xs text-theme-text-tertiary text-center">
198
+ +{availableKinds.length - 6}
199
+ </span>
200
+ )}
201
+ </div>
202
+ </div>
203
+ )
204
+ }
205
+
206
+ return (
207
+ <div className="w-56 flex flex-col bg-theme-surface/90 backdrop-blur border-r border-theme-border overflow-hidden">
208
+ {/* Header */}
209
+ <div className="flex items-center justify-between px-3 py-2 border-b border-theme-border">
210
+ <span className="text-sm font-medium text-theme-text-secondary">Filters</span>
211
+ <button
212
+ onClick={onToggleCollapse}
213
+ className="p-1 text-theme-text-secondary hover:text-theme-text-primary hover:bg-theme-elevated rounded transition-colors"
214
+ title="Collapse sidebar"
215
+ >
216
+ <ChevronLeft className="w-4 h-4" />
217
+ </button>
218
+ </div>
219
+
220
+ {/* Quick actions */}
221
+ <div className="flex gap-1 px-3 py-2 border-b border-theme-border">
222
+ <button
223
+ onClick={onShowAll}
224
+ disabled={allVisible}
225
+ className={clsx(
226
+ 'flex-1 flex items-center justify-center gap-1 px-2 py-1.5 text-xs rounded transition-colors',
227
+ allVisible
228
+ ? 'bg-theme-elevated/50 text-theme-text-tertiary cursor-not-allowed'
229
+ : 'bg-theme-elevated text-theme-text-secondary hover:bg-theme-hover hover:text-theme-text-primary'
230
+ )}
231
+ >
232
+ <Eye className="w-3 h-3" />
233
+ Show All
234
+ </button>
235
+ <button
236
+ onClick={onHideAll}
237
+ disabled={noneVisible}
238
+ className={clsx(
239
+ 'flex-1 flex items-center justify-center gap-1 px-2 py-1.5 text-xs rounded transition-colors',
240
+ noneVisible
241
+ ? 'bg-theme-elevated/50 text-theme-text-tertiary cursor-not-allowed'
242
+ : 'bg-theme-elevated text-theme-text-secondary hover:bg-theme-hover hover:text-theme-text-primary'
243
+ )}
244
+ >
245
+ <EyeOff className="w-3 h-3" />
246
+ Hide All
247
+ </button>
248
+ </div>
249
+
250
+ {/* Kind toggles by category */}
251
+ <div className="flex-1 overflow-y-auto">
252
+ {CATEGORIES.map(category => {
253
+ const kinds = kindsByCategory.get(category.id)
254
+ if (!kinds || kinds.length === 0) return null
255
+
256
+ return (
257
+ <div key={category.id} className="px-2 py-2">
258
+ <div className="text-xs font-medium text-theme-text-tertiary uppercase tracking-wider px-1 mb-1">
259
+ {category.label}
260
+ </div>
261
+ <div className="space-y-0.5">
262
+ {kinds.map(({ kind, label, icon: Icon, color }) => {
263
+ const count = kindCounts.get(kind) || 0
264
+ const isVisible = visibleKinds.has(kind)
265
+
266
+ return (
267
+ <button
268
+ key={kind}
269
+ onClick={() => onToggleKind(kind)}
270
+ className={clsx(
271
+ 'w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-left transition-colors',
272
+ isVisible
273
+ ? 'bg-theme-elevated/70 text-theme-text-primary'
274
+ : 'text-theme-text-secondary hover:bg-theme-elevated/40 hover:text-theme-text-secondary'
275
+ )}
276
+ >
277
+ <Icon className={clsx('w-4 h-4 shrink-0', isVisible ? color : 'text-theme-text-tertiary')} />
278
+ <span className="flex-1 text-sm truncate">{label}</span>
279
+ <span className={clsx(
280
+ 'text-xs px-1.5 py-0.5 rounded',
281
+ isVisible ? 'bg-theme-hover text-theme-text-secondary' : 'bg-theme-elevated/50 text-theme-text-tertiary'
282
+ )}>
283
+ {count}
284
+ </span>
285
+ </button>
286
+ )
287
+ })}
288
+ </div>
289
+ </div>
290
+ )
291
+ })}
292
+
293
+ {/* Auto-hidden kinds section (for large clusters) */}
294
+ {hiddenKinds.length > 0 && (
295
+ <div className="px-2 py-2 border-t border-theme-border/50">
296
+ <div className="flex items-center gap-1 text-xs font-medium text-amber-400/80 uppercase tracking-wider px-1 mb-1">
297
+ <Zap className="w-3 h-3" />
298
+ Hidden for Performance
299
+ </div>
300
+ <div className="space-y-0.5">
301
+ {hiddenKinds.map(kindName => {
302
+ const kindConfig = RESOURCE_KINDS.find(k => k.kind === kindName)
303
+ if (!kindConfig) return null
304
+
305
+ const { kind, label, icon: Icon } = kindConfig
306
+ const isConfirmed = confirmedKinds.has(kind)
307
+ const isPending = pendingConfirmKind === kind
308
+
309
+ return (
310
+ <div key={kind} className="relative">
311
+ <button
312
+ onClick={() => {
313
+ if (isConfirmed) {
314
+ // Already confirmed - just toggle
315
+ onEnableHiddenKind?.(kind)
316
+ } else {
317
+ // Show confirmation
318
+ setPendingConfirmKind(kind)
319
+ }
320
+ }}
321
+ className="w-full flex items-center gap-2 px-2 py-1.5 rounded-md text-left transition-colors text-theme-text-tertiary hover:bg-amber-500/10 hover:text-amber-400/80"
322
+ title="Hidden for performance - click to show"
323
+ >
324
+ <Icon className="w-4 h-4 shrink-0 opacity-50" />
325
+ <span className="flex-1 text-sm truncate opacity-70">{label}</span>
326
+ <EyeOff className="w-3 h-3 opacity-50" />
327
+ </button>
328
+
329
+ {/* Confirmation popup */}
330
+ {isPending && (
331
+ <div className="absolute left-0 right-0 top-full mt-1 z-50 bg-theme-surface border border-amber-500/30 rounded-lg shadow-lg p-3">
332
+ <div className="flex items-start gap-2 mb-2">
333
+ <AlertTriangle className="w-4 h-4 text-amber-400 shrink-0 mt-0.5" />
334
+ <div className="text-xs text-theme-text-secondary">
335
+ <p className="font-medium text-amber-400">Performance Warning</p>
336
+ <p className="mt-1">
337
+ Showing {label}s may slow down the topology view in large clusters.
338
+ </p>
339
+ </div>
340
+ </div>
341
+ <div className="flex gap-2">
342
+ <button
343
+ onClick={() => {
344
+ setConfirmedKinds(prev => new Set(prev).add(kind))
345
+ setPendingConfirmKind(null)
346
+ onEnableHiddenKind?.(kind)
347
+ }}
348
+ className="flex-1 px-2 py-1 text-xs bg-amber-500/20 text-amber-400 hover:bg-amber-500/30 rounded transition-colors"
349
+ >
350
+ Show Anyway
351
+ </button>
352
+ <button
353
+ onClick={() => setPendingConfirmKind(null)}
354
+ className="flex-1 px-2 py-1 text-xs bg-theme-elevated text-theme-text-secondary hover:bg-theme-hover rounded transition-colors"
355
+ >
356
+ Cancel
357
+ </button>
358
+ </div>
359
+ </div>
360
+ )}
361
+ </div>
362
+ )
363
+ })}
364
+ </div>
365
+ </div>
366
+ )}
367
+ </div>
368
+
369
+ {/* Footer stats */}
370
+ <div className="px-3 py-2 border-t border-theme-border bg-theme-surface/50">
371
+ <div className="text-xs text-theme-text-tertiary">
372
+ Showing {availableKinds.filter(k => visibleKinds.has(k.kind)).reduce((sum, k) => sum + (kindCounts.get(k.kind) || 0), 0)} of {nodes.length} resources
373
+ </div>
374
+ </div>
375
+ </div>
376
+ )
377
+ })