@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,304 @@
1
+ import { useState, useCallback, useEffect, useRef, useMemo } from 'react'
2
+ import { Search, X, ChevronRight } from 'lucide-react'
3
+ import { getTopologyIcon } from '../../utils/resource-icons'
4
+ import { clsx } from 'clsx'
5
+ import type { TopologyNode } from '../../types'
6
+ import { useRegisterShortcut } from '../../hooks/useKeyboardShortcuts'
7
+
8
+ interface TopologySearchProps {
9
+ nodes: TopologyNode[]
10
+ onNodeSelect?: (node: TopologyNode) => void
11
+ onZoomToNode?: (nodeId: string) => void
12
+ }
13
+
14
+ // Icon mapping for different resource kinds
15
+ function getNodeIcon(kind: string) {
16
+ return getTopologyIcon(kind)
17
+ }
18
+
19
+ // Color mapping for different resource kinds
20
+ function getKindColor(kind: string): string {
21
+ switch (kind) {
22
+ case 'Ingress':
23
+ case 'Gateway':
24
+ return 'text-purple-400'
25
+ case 'HTTPRoute':
26
+ case 'GRPCRoute':
27
+ case 'TCPRoute':
28
+ case 'TLSRoute':
29
+ return 'text-purple-300'
30
+ case 'Service':
31
+ return 'text-blue-400'
32
+ case 'Deployment':
33
+ case 'Rollout':
34
+ return 'text-emerald-400'
35
+ case 'DaemonSet':
36
+ return 'text-teal-400'
37
+ case 'StatefulSet':
38
+ return 'text-cyan-400'
39
+ case 'ReplicaSet':
40
+ return 'text-green-400'
41
+ case 'Pod':
42
+ case 'PodGroup':
43
+ return 'text-lime-400'
44
+ case 'ConfigMap':
45
+ return 'text-amber-400'
46
+ case 'Secret':
47
+ return 'text-red-400'
48
+ default:
49
+ return 'text-theme-text-secondary'
50
+ }
51
+ }
52
+
53
+ export function TopologySearch({ nodes, onNodeSelect, onZoomToNode }: TopologySearchProps) {
54
+ const [isOpen, setIsOpen] = useState(false)
55
+ const [query, setQuery] = useState('')
56
+ const [selectedIndex, setSelectedIndex] = useState(0)
57
+ const inputRef = useRef<HTMLInputElement>(null)
58
+ const resultsRef = useRef<HTMLDivElement>(null)
59
+
60
+ // Filter nodes based on query
61
+ const filteredNodes = useMemo(() => {
62
+ if (!query.trim()) return []
63
+
64
+ const lowerQuery = query.toLowerCase()
65
+ return nodes
66
+ .filter(node => {
67
+ const name = node.name.toLowerCase()
68
+ const kind = node.kind.toLowerCase()
69
+ const namespace = (node.data.namespace as string || '').toLowerCase()
70
+ return (
71
+ name.includes(lowerQuery) ||
72
+ kind.includes(lowerQuery) ||
73
+ namespace.includes(lowerQuery) ||
74
+ `${kind}/${name}`.includes(lowerQuery) ||
75
+ `${namespace}/${name}`.includes(lowerQuery)
76
+ )
77
+ })
78
+ .slice(0, 10) // Limit results
79
+ }, [nodes, query])
80
+
81
+ // Reset selection when results change
82
+ useEffect(() => {
83
+ setSelectedIndex(0)
84
+ }, [filteredNodes])
85
+
86
+ // Scroll selected item into view
87
+ useEffect(() => {
88
+ if (resultsRef.current && filteredNodes.length > 0) {
89
+ const selectedElement = resultsRef.current.children[selectedIndex] as HTMLElement
90
+ if (selectedElement) {
91
+ selectedElement.scrollIntoView({ block: 'nearest' })
92
+ }
93
+ }
94
+ }, [selectedIndex, filteredNodes.length])
95
+
96
+ // Keyboard shortcuts for topology search
97
+ useRegisterShortcut({
98
+ id: 'topology-search',
99
+ keys: '/',
100
+ description: 'Search topology',
101
+ category: 'Search',
102
+ scope: 'topology',
103
+ handler: () => {
104
+ setIsOpen(true)
105
+ setTimeout(() => inputRef.current?.focus(), 0)
106
+ },
107
+ })
108
+ useRegisterShortcut({
109
+ id: 'topology-search-close',
110
+ keys: 'Escape',
111
+ description: 'Close search',
112
+ category: 'Topology',
113
+ scope: 'topology',
114
+ handler: () => { if (isOpen) handleClose() },
115
+ enabled: isOpen,
116
+ })
117
+
118
+ // Navigate to node and zoom
119
+ const navigateToNode = useCallback((node: TopologyNode) => {
120
+ // Zoom to the node
121
+ onZoomToNode?.(node.id)
122
+
123
+ // Notify parent
124
+ onNodeSelect?.(node)
125
+
126
+ // Close search
127
+ handleClose()
128
+ }, [onZoomToNode, onNodeSelect])
129
+
130
+ // Handle keyboard navigation in results
131
+ const handleInputKeyDown = useCallback((e: React.KeyboardEvent) => {
132
+ if (!filteredNodes.length) return
133
+
134
+ switch (e.key) {
135
+ case 'ArrowDown':
136
+ e.preventDefault()
137
+ setSelectedIndex(i => Math.min(i + 1, filteredNodes.length - 1))
138
+ break
139
+ case 'ArrowUp':
140
+ e.preventDefault()
141
+ setSelectedIndex(i => Math.max(i - 1, 0))
142
+ break
143
+ case 'Enter':
144
+ e.preventDefault()
145
+ if (filteredNodes[selectedIndex]) {
146
+ navigateToNode(filteredNodes[selectedIndex])
147
+ }
148
+ break
149
+ }
150
+ }, [filteredNodes, selectedIndex, navigateToNode])
151
+
152
+ const handleClose = useCallback(() => {
153
+ setIsOpen(false)
154
+ setQuery('')
155
+ setSelectedIndex(0)
156
+ }, [])
157
+
158
+ const handleOpen = useCallback(() => {
159
+ setIsOpen(true)
160
+ setTimeout(() => inputRef.current?.focus(), 0)
161
+ }, [])
162
+
163
+ return (
164
+ <>
165
+ {/* Search trigger button */}
166
+ <button
167
+ onClick={handleOpen}
168
+ className="absolute top-4 left-4 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"
169
+ >
170
+ <Search className="w-4 h-4" />
171
+ <span className="text-sm">Search</span>
172
+ <kbd className="hidden sm:inline-flex items-center gap-0.5 px-1.5 py-0.5 text-xs bg-theme-elevated rounded border border-theme-border-light">
173
+ <span className="text-[10px]">⌘</span>K
174
+ </kbd>
175
+ </button>
176
+
177
+ {/* Search modal */}
178
+ {isOpen && (
179
+ <div className="absolute inset-0 z-50 flex items-start justify-center pt-[10vh]">
180
+ {/* Backdrop */}
181
+ <div
182
+ className="absolute inset-0 bg-theme-base/60 backdrop-blur-sm"
183
+ onClick={handleClose}
184
+ />
185
+
186
+ {/* Search panel */}
187
+ <div className="relative w-full max-w-lg mx-4 bg-theme-surface border border-theme-border rounded-xl shadow-2xl overflow-hidden">
188
+ {/* Search input */}
189
+ <div className="flex items-center gap-3 px-4 py-3 border-b border-theme-border">
190
+ <Search className="w-5 h-5 text-theme-text-secondary" />
191
+ <input
192
+ ref={inputRef}
193
+ type="text"
194
+ value={query}
195
+ onChange={(e) => setQuery(e.target.value)}
196
+ onKeyDown={handleInputKeyDown}
197
+ placeholder="Search resources by name, kind, or namespace..."
198
+ className="flex-1 bg-transparent text-theme-text-primary placeholder-theme-text-disabled outline-none text-sm"
199
+ autoFocus
200
+ />
201
+ {query && (
202
+ <button
203
+ onClick={() => setQuery('')}
204
+ className="p-1 text-theme-text-secondary hover:text-theme-text-primary"
205
+ >
206
+ <X className="w-4 h-4" />
207
+ </button>
208
+ )}
209
+ <kbd className="px-1.5 py-0.5 text-xs text-theme-text-tertiary bg-theme-elevated rounded border border-theme-border-light">
210
+ ESC
211
+ </kbd>
212
+ </div>
213
+
214
+ {/* Results */}
215
+ <div ref={resultsRef} className="max-h-[60vh] overflow-y-auto">
216
+ {query && filteredNodes.length === 0 && (
217
+ <div className="px-4 py-8 text-center text-theme-text-tertiary">
218
+ <Search className="w-8 h-8 mx-auto mb-2 opacity-50" />
219
+ <p>No resources found for "{query}"</p>
220
+ </div>
221
+ )}
222
+
223
+ {filteredNodes.map((node, index) => {
224
+ const Icon = getNodeIcon(node.kind)
225
+ const isSelected = index === selectedIndex
226
+ const namespace = node.data.namespace as string || ''
227
+
228
+ return (
229
+ <button
230
+ key={node.id}
231
+ onClick={() => navigateToNode(node)}
232
+ onMouseEnter={() => setSelectedIndex(index)}
233
+ className={clsx(
234
+ 'w-full flex items-center gap-3 px-4 py-3 text-left transition-colors',
235
+ isSelected ? 'bg-theme-elevated/50' : 'hover:bg-theme-elevated/30'
236
+ )}
237
+ >
238
+ <div className={clsx(
239
+ 'flex items-center justify-center w-8 h-8 rounded-lg',
240
+ isSelected ? 'bg-blue-500/20' : 'bg-theme-elevated/50'
241
+ )}>
242
+ <Icon className={clsx('w-4 h-4', getKindColor(node.kind))} />
243
+ </div>
244
+
245
+ <div className="flex-1 min-w-0">
246
+ <div className="flex items-center gap-2">
247
+ <span className="font-medium text-theme-text-primary truncate">{node.name}</span>
248
+ <span className={clsx(
249
+ 'px-1.5 py-0.5 text-xs rounded',
250
+ 'bg-theme-elevated text-theme-text-secondary'
251
+ )}>
252
+ {node.kind}
253
+ </span>
254
+ </div>
255
+ {namespace && (
256
+ <div className="text-xs text-theme-text-tertiary truncate">
257
+ {namespace}
258
+ </div>
259
+ )}
260
+ </div>
261
+
262
+ <ChevronRight className={clsx(
263
+ 'w-4 h-4 transition-opacity',
264
+ isSelected ? 'text-theme-text-secondary opacity-100' : 'opacity-0'
265
+ )} />
266
+ </button>
267
+ )
268
+ })}
269
+ </div>
270
+
271
+ {/* Footer hint */}
272
+ {filteredNodes.length > 0 && (
273
+ <div className="px-4 py-2 border-t border-theme-border bg-theme-surface/50">
274
+ <div className="flex items-center gap-4 text-xs text-theme-text-tertiary">
275
+ <span className="flex items-center gap-1">
276
+ <kbd className="px-1 py-0.5 bg-theme-elevated rounded">↑</kbd>
277
+ <kbd className="px-1 py-0.5 bg-theme-elevated rounded">↓</kbd>
278
+ <span>Navigate</span>
279
+ </span>
280
+ <span className="flex items-center gap-1">
281
+ <kbd className="px-1.5 py-0.5 bg-theme-elevated rounded">Enter</kbd>
282
+ <span>Zoom to resource</span>
283
+ </span>
284
+ </div>
285
+ </div>
286
+ )}
287
+
288
+ {/* Empty state with hints */}
289
+ {!query && (
290
+ <div className="px-4 py-6 text-center text-theme-text-tertiary">
291
+ <p className="text-sm mb-3">Search for resources in the topology</p>
292
+ <div className="flex flex-wrap justify-center gap-2 text-xs">
293
+ <span className="px-2 py-1 bg-theme-elevated/50 rounded">nginx</span>
294
+ <span className="px-2 py-1 bg-theme-elevated/50 rounded">service/api</span>
295
+ <span className="px-2 py-1 bg-theme-elevated/50 rounded">production</span>
296
+ </div>
297
+ </div>
298
+ )}
299
+ </div>
300
+ </div>
301
+ )}
302
+ </>
303
+ )
304
+ }
@@ -0,0 +1,7 @@
1
+ export { TopologyGraph } from './TopologyGraph'
2
+ export { K8sResourceNode, NODE_DIMENSIONS, DEFAULT_NODE_DIMENSIONS } from './K8sResourceNode'
3
+ export { GroupNode } from './GroupNode'
4
+ export { TopologySearch } from './TopologySearch'
5
+ export { TopologyFilterSidebar } from './TopologyFilterSidebar'
6
+ export { TopologyControls } from './TopologyControls'
7
+ export { buildHierarchicalElkGraph, applyHierarchicalLayout, getGroupKey, setLayoutEngine } from './layout'