@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,282 @@
1
+ import { Server, HardDrive, Globe, Tag, Activity, ExternalLink } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
4
+ import { MetricsChart } from '../../ui/MetricsChart'
5
+ import { formatMemoryString } from '../../../utils/format'
6
+ import type { MetricsDataPoint } from '../../../types/core'
7
+
8
+ interface NodeRendererProps {
9
+ data: any
10
+ relationships?: { pods?: any[] }
11
+ onViewPods?: () => void
12
+ metrics?: { usage?: { cpu: string; memory: string }; timestamp?: string }
13
+ metricsHistory?: { dataPoints?: MetricsDataPoint[]; collectionError?: string }
14
+ hideMetricsServer?: boolean
15
+ }
16
+
17
+ // Helper to handle undefined values
18
+ function formatMemory(value: string | undefined): string {
19
+ if (!value) return '-'
20
+ return formatMemoryString(value)
21
+ }
22
+
23
+ // Format storage values the same way as memory
24
+ function formatStorage(value: string | undefined): string {
25
+ return formatMemory(value)
26
+ }
27
+
28
+ // Extract problems from node status and spec
29
+ function getNodeProblems(data: any): string[] {
30
+ const problems: string[] = []
31
+ const conditions = data.status?.conditions || []
32
+ const spec = data.spec || {}
33
+
34
+ // Check if unschedulable
35
+ if (spec.unschedulable) {
36
+ problems.push('Node is cordoned (unschedulable)')
37
+ }
38
+
39
+ for (const cond of conditions) {
40
+ // NotReady is a problem when status is not True
41
+ if (cond.type === 'Ready' && cond.status !== 'True') {
42
+ problems.push(`Node is NotReady${cond.message ? ': ' + cond.message : ''}`)
43
+ }
44
+
45
+ // These conditions are problems when True
46
+ if (cond.status === 'True') {
47
+ if (cond.type === 'DiskPressure') {
48
+ problems.push(`Disk pressure${cond.message ? ': ' + cond.message : ''}`)
49
+ }
50
+ if (cond.type === 'MemoryPressure') {
51
+ problems.push(`Memory pressure${cond.message ? ': ' + cond.message : ''}`)
52
+ }
53
+ if (cond.type === 'PIDPressure') {
54
+ problems.push(`PID pressure${cond.message ? ': ' + cond.message : ''}`)
55
+ }
56
+ if (cond.type === 'NetworkUnavailable') {
57
+ problems.push(`Network unavailable${cond.message ? ': ' + cond.message : ''}`)
58
+ }
59
+ }
60
+ }
61
+
62
+ return problems
63
+ }
64
+
65
+ export function NodeRenderer({ data, relationships, onViewPods, metrics, metricsHistory, hideMetricsServer }: NodeRendererProps) {
66
+ const status = data.status || {}
67
+ const spec = data.spec || {}
68
+ const metadata = data.metadata || {}
69
+ const labels = metadata.labels || {}
70
+ const nodeInfo = status.nodeInfo || {}
71
+ const capacity = status.capacity || {}
72
+ const allocatable = status.allocatable || {}
73
+ const addresses = status.addresses || []
74
+ const taints = spec.taints || []
75
+
76
+ // Check for problems
77
+ const problems = getNodeProblems(data)
78
+ const hasProblems = problems.length > 0
79
+
80
+ // Extract platform info from labels
81
+ const instanceType = labels['node.kubernetes.io/instance-type']
82
+ const zone = labels['topology.kubernetes.io/zone']
83
+ const region = labels['topology.kubernetes.io/region']
84
+ const nodePool = labels['cloud.google.com/gke-nodepool'] || labels['eks.amazonaws.com/nodegroup']
85
+ const machineFamily = labels['cloud.google.com/machine-family']
86
+ const hasPlatformInfo = instanceType || zone || region || nodePool || machineFamily
87
+
88
+ return (
89
+ <>
90
+ {/* Problems alert - shown at top when there are issues */}
91
+ {hasProblems && (
92
+ <AlertBanner variant="error" title="Issues Detected" items={problems} />
93
+ )}
94
+
95
+ {/* Node Info */}
96
+ <Section title="Node Info" icon={Server}>
97
+ <PropertyList>
98
+ <Property label="OS" value={nodeInfo.osImage} />
99
+ <Property label="Architecture" value={nodeInfo.architecture} />
100
+ <Property label="Kernel" value={nodeInfo.kernelVersion} />
101
+ <Property label="Container Runtime" value={nodeInfo.containerRuntimeVersion} />
102
+ <Property label="Kubelet" value={nodeInfo.kubeletVersion} />
103
+ <Property label="Kube-Proxy" value={nodeInfo.kubeProxyVersion} />
104
+ </PropertyList>
105
+ </Section>
106
+
107
+ {/* Capacity */}
108
+ <Section title="Capacity" icon={HardDrive}>
109
+ <div className="space-y-1">
110
+ <div className="grid grid-cols-4 gap-2 text-xs text-theme-text-tertiary font-medium mb-2">
111
+ <span>Resource</span>
112
+ <span>Capacity</span>
113
+ <span>Allocatable</span>
114
+ <span>In Use</span>
115
+ </div>
116
+ <div className="grid grid-cols-4 gap-2 text-sm">
117
+ <span className="text-theme-text-secondary">CPU</span>
118
+ <span className="text-theme-text-primary">{capacity.cpu || '-'}</span>
119
+ <span className="text-theme-text-primary">{allocatable.cpu || '-'}</span>
120
+ <span className="text-theme-text-primary font-medium">{metrics?.usage?.cpu || '-'}</span>
121
+ </div>
122
+ <div className="grid grid-cols-4 gap-2 text-sm">
123
+ <span className="text-theme-text-secondary">Memory</span>
124
+ <span className="text-theme-text-primary">{formatMemory(capacity.memory)}</span>
125
+ <span className="text-theme-text-primary">{formatMemory(allocatable.memory)}</span>
126
+ <span className="text-theme-text-primary font-medium">{metrics?.usage?.memory ? formatMemory(metrics.usage.memory) : '-'}</span>
127
+ </div>
128
+ <div className="grid grid-cols-4 gap-2 text-sm">
129
+ <span className="text-theme-text-secondary">Pods</span>
130
+ <span className="text-theme-text-primary">{capacity.pods || '-'}</span>
131
+ <span className="text-theme-text-primary">{allocatable.pods || '-'}</span>
132
+ <span className="text-theme-text-primary font-medium">{relationships?.pods?.length ?? '-'}</span>
133
+ </div>
134
+ <div className="grid grid-cols-4 gap-2 text-sm">
135
+ <span className="text-theme-text-secondary">Ephemeral Storage</span>
136
+ <span className="text-theme-text-primary">{formatStorage(capacity['ephemeral-storage'])}</span>
137
+ <span className="text-theme-text-primary">{formatStorage(allocatable['ephemeral-storage'])}</span>
138
+ <span className="text-theme-text-primary">-</span>
139
+ </div>
140
+ </div>
141
+ {onViewPods && (
142
+ <div className="mt-3 pt-3 border-t border-theme-border">
143
+ <button
144
+ onClick={onViewPods}
145
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-blue-400 hover:text-blue-300 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 rounded transition-colors"
146
+ >
147
+ <ExternalLink className="w-3 h-3" />
148
+ View Pods
149
+ </button>
150
+ </div>
151
+ )}
152
+ </Section>
153
+
154
+ {/* Resource Usage (from metrics-server) — hidden when Prometheus has CPU/memory data */}
155
+ {!hideMetricsServer && (metrics?.usage || metricsHistory?.dataPoints?.length || metricsHistory?.collectionError) && (
156
+ <Section title="Resource Usage" icon={Activity} defaultExpanded>
157
+ {metricsHistory?.collectionError && !metricsHistory?.dataPoints?.length && (
158
+ <div className="mb-3 rounded-lg border border-yellow-500/30 bg-yellow-500/5 px-3 py-2 text-xs text-yellow-400">
159
+ <span className="font-medium">Metrics collection error:</span>{' '}
160
+ <span className="break-all">{metricsHistory.collectionError}</span>
161
+ </div>
162
+ )}
163
+ {metricsHistory?.dataPoints && metricsHistory.dataPoints.length > 0 ? (
164
+ <div className="space-y-4">
165
+ {/* CPU Usage with Chart */}
166
+ <div className="bg-theme-elevated/30 rounded-lg p-3">
167
+ <div className="text-xs text-theme-text-tertiary mb-1 flex items-center justify-between">
168
+ <span>CPU</span>
169
+ <span className="text-theme-text-quaternary">
170
+ {allocatable.cpu || capacity.cpu || '?'} allocatable
171
+ </span>
172
+ </div>
173
+ <MetricsChart
174
+ dataPoints={metricsHistory.dataPoints}
175
+ type="cpu"
176
+ height={60}
177
+ showAxis={true}
178
+ />
179
+ </div>
180
+
181
+ {/* Memory Usage with Chart */}
182
+ <div className="bg-theme-elevated/30 rounded-lg p-3">
183
+ <div className="text-xs text-theme-text-tertiary mb-1 flex items-center justify-between">
184
+ <span>Memory</span>
185
+ <span className="text-theme-text-quaternary">
186
+ {formatMemory(allocatable.memory) || formatMemory(capacity.memory) || '?'} allocatable
187
+ </span>
188
+ </div>
189
+ <MetricsChart
190
+ dataPoints={metricsHistory.dataPoints}
191
+ type="memory"
192
+ height={60}
193
+ showAxis={true}
194
+ />
195
+ </div>
196
+ </div>
197
+ ) : metrics?.usage ? (
198
+ /* Fallback to simple display if no history yet */
199
+ <div className="space-y-3">
200
+ <div className="bg-theme-elevated/30 rounded-lg p-3">
201
+ <div className="flex items-center justify-between mb-2">
202
+ <span className="text-sm font-medium text-theme-text-primary">CPU</span>
203
+ </div>
204
+ <div className="flex items-baseline gap-2">
205
+ <span className="text-lg font-medium text-blue-400">{metrics.usage.cpu}</span>
206
+ <span className="text-sm text-theme-text-tertiary">
207
+ / {allocatable.cpu || capacity.cpu || '?'} allocatable
208
+ </span>
209
+ </div>
210
+ </div>
211
+ <div className="bg-theme-elevated/30 rounded-lg p-3">
212
+ <div className="flex items-center justify-between mb-2">
213
+ <span className="text-sm font-medium text-theme-text-primary">Memory</span>
214
+ </div>
215
+ <div className="flex items-baseline gap-2">
216
+ <span className="text-lg font-medium text-purple-400">{formatMemory(metrics.usage.memory)}</span>
217
+ <span className="text-sm text-theme-text-tertiary">
218
+ / {formatMemory(allocatable.memory) || formatMemory(capacity.memory) || '?'} allocatable
219
+ </span>
220
+ </div>
221
+ </div>
222
+ </div>
223
+ ) : (
224
+ <div className="text-xs text-theme-text-tertiary">Collecting metrics data...</div>
225
+ )}
226
+ {metrics?.timestamp && (
227
+ <div className="mt-2 text-xs text-theme-text-tertiary">
228
+ Last updated: {new Date(metrics.timestamp).toLocaleTimeString()}
229
+ </div>
230
+ )}
231
+ </Section>
232
+ )}
233
+
234
+ {/* Addresses */}
235
+ {addresses.length > 0 && (
236
+ <Section title="Addresses" icon={Globe}>
237
+ <PropertyList>
238
+ {addresses.map((addr: any) => (
239
+ <Property key={`${addr.type}-${addr.address}`} label={addr.type} value={addr.address} />
240
+ ))}
241
+ </PropertyList>
242
+ </Section>
243
+ )}
244
+
245
+ {/* Platform Info */}
246
+ {hasPlatformInfo && (
247
+ <Section title="Platform" icon={Tag}>
248
+ <PropertyList>
249
+ <Property label="Instance Type" value={instanceType} />
250
+ <Property label="Zone" value={zone} />
251
+ <Property label="Region" value={region} />
252
+ <Property label="Node Pool" value={nodePool} />
253
+ <Property label="Machine Family" value={machineFamily} />
254
+ </PropertyList>
255
+ </Section>
256
+ )}
257
+
258
+ {/* Taints */}
259
+ {taints.length > 0 && (
260
+ <Section title={`Taints (${taints.length})`} defaultExpanded={taints.length <= 5}>
261
+ <div className="space-y-1">
262
+ {taints.map((taint: any, i: number) => (
263
+ <div key={`${taint.key}-${taint.effect}-${i}`} className="text-sm">
264
+ <span className={clsx(
265
+ 'px-2 py-0.5 rounded text-xs',
266
+ taint.effect === 'NoSchedule' ? 'bg-yellow-500/20 text-yellow-400' :
267
+ taint.effect === 'NoExecute' ? 'bg-red-500/20 text-red-400' :
268
+ 'bg-blue-500/20 text-blue-400'
269
+ )}>
270
+ {taint.key}{taint.value ? `=${taint.value}` : ''}:{taint.effect}
271
+ </span>
272
+ </div>
273
+ ))}
274
+ </div>
275
+ </Section>
276
+ )}
277
+
278
+ {/* Conditions */}
279
+ <ConditionsSection conditions={status.conditions} />
280
+ </>
281
+ )
282
+ }
@@ -0,0 +1,153 @@
1
+ import { Package, AlertTriangle, CheckCircle2 } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
4
+ import { formatAge } from '../resource-utils'
5
+ import { formatBytes } from '../../../utils/format'
6
+ import { GitOpsStatusBadge, SyncCountdown } from '../../gitops'
7
+ import { fluxConditionsToGitOpsStatus, type FluxCondition } from '../../../types/gitops'
8
+
9
+ interface OCIRepositoryRendererProps {
10
+ data: any
11
+ }
12
+
13
+ export function OCIRepositoryRenderer({ data }: OCIRepositoryRendererProps) {
14
+ const status = data.status || {}
15
+ const spec = data.spec || {}
16
+ const conditions = (status.conditions || []) as FluxCondition[]
17
+ const artifact = status.artifact || {}
18
+
19
+ // Convert to unified GitOps status
20
+ const gitOpsStatus = fluxConditionsToGitOpsStatus(conditions, spec.suspend === true)
21
+
22
+ // Problem detection
23
+ const problems: Array<{ color: 'red' | 'yellow'; message: string }> = []
24
+
25
+ if (gitOpsStatus.suspended) {
26
+ problems.push({ color: 'yellow', message: 'OCIRepository is suspended' })
27
+ }
28
+
29
+ if (gitOpsStatus.health === 'Degraded' && gitOpsStatus.message) {
30
+ problems.push({ color: 'red', message: gitOpsStatus.message })
31
+ }
32
+
33
+ // Extract repository info
34
+ const url = spec.url || ''
35
+ const ref = spec.ref || {}
36
+
37
+ return (
38
+ <>
39
+ {/* Problem alerts */}
40
+ {problems.map((problem, i) => (
41
+ <div
42
+ key={i}
43
+ className={clsx(
44
+ 'mb-4 p-3 border rounded-lg',
45
+ problem.color === 'red'
46
+ ? 'bg-red-500/10 border-red-500/30'
47
+ : 'bg-yellow-500/10 border-yellow-500/30'
48
+ )}
49
+ >
50
+ <div className="flex items-start gap-2">
51
+ <AlertTriangle
52
+ className={clsx(
53
+ 'w-4 h-4 mt-0.5 shrink-0',
54
+ problem.color === 'red' ? 'text-red-400' : 'text-yellow-400'
55
+ )}
56
+ />
57
+ <div className="flex-1 min-w-0">
58
+ <div
59
+ className={clsx(
60
+ 'text-sm font-medium',
61
+ problem.color === 'red' ? 'text-red-400' : 'text-yellow-400'
62
+ )}
63
+ >
64
+ {problem.color === 'red' ? 'Issue Detected' : 'Warning'}
65
+ </div>
66
+ <div
67
+ className={clsx(
68
+ 'text-xs mt-1',
69
+ problem.color === 'red' ? 'text-red-300/80' : 'text-yellow-300/80'
70
+ )}
71
+ >
72
+ {problem.message}
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ ))}
78
+
79
+ {/* Status section */}
80
+ <Section title="Status">
81
+ <div className="space-y-3">
82
+ <GitOpsStatusBadge status={gitOpsStatus} showHealth={false} />
83
+ {spec.interval && (
84
+ <SyncCountdown
85
+ interval={spec.interval}
86
+ lastSyncTime={status.lastHandledReconcileAt}
87
+ suspended={gitOpsStatus.suspended}
88
+ />
89
+ )}
90
+ </div>
91
+ </Section>
92
+
93
+ {/* Source section */}
94
+ <Section title="Source" icon={Package}>
95
+ <PropertyList>
96
+ <Property label="URL" value={url} />
97
+ {ref.tag && <Property label="Tag" value={ref.tag} />}
98
+ {ref.semver && <Property label="Semver" value={ref.semver} />}
99
+ {ref.digest && <Property label="Digest" value={ref.digest} />}
100
+ {spec.provider && <Property label="Provider" value={spec.provider} />}
101
+ {spec.secretRef?.name && (
102
+ <Property label="Secret" value={spec.secretRef.name} />
103
+ )}
104
+ {spec.serviceAccountName && (
105
+ <Property label="Service Account" value={spec.serviceAccountName} />
106
+ )}
107
+ {spec.insecure && <Property label="Insecure" value="Yes" />}
108
+ </PropertyList>
109
+ </Section>
110
+
111
+ {/* Artifact section */}
112
+ {artifact.revision && (
113
+ <Section title="Latest Artifact" icon={CheckCircle2}>
114
+ <PropertyList>
115
+ <Property label="Revision" value={artifact.revision} />
116
+ <Property label="Digest" value={artifact.digest} />
117
+ <Property
118
+ label="Last Updated"
119
+ value={artifact.lastUpdateTime ? formatAge(artifact.lastUpdateTime) : '-'}
120
+ />
121
+ {artifact.size && (
122
+ <Property label="Size" value={formatBytes(artifact.size)} />
123
+ )}
124
+ {artifact.metadata && Object.keys(artifact.metadata).length > 0 && (
125
+ <Property
126
+ label="Metadata"
127
+ value={Object.entries(artifact.metadata).map(([k, v]) => `${k}=${v}`).join(', ')}
128
+ />
129
+ )}
130
+ </PropertyList>
131
+ </Section>
132
+ )}
133
+
134
+ {/* Additional Info */}
135
+ {status.observedGeneration !== undefined && (
136
+ <Section title="Additional Info" defaultExpanded={false}>
137
+ <PropertyList>
138
+ <Property label="Observed Generation" value={status.observedGeneration} />
139
+ {status.lastHandledReconcileAt && (
140
+ <Property
141
+ label="Last Reconciled"
142
+ value={formatAge(status.lastHandledReconcileAt)}
143
+ />
144
+ )}
145
+ </PropertyList>
146
+ </Section>
147
+ )}
148
+
149
+ {/* Conditions section */}
150
+ <ConditionsSection conditions={conditions} />
151
+ </>
152
+ )
153
+ }
@@ -0,0 +1,108 @@
1
+ import { ShoppingCart, AlertTriangle, Globe, Shield } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
4
+
5
+ function getOrderStateBadge(state: string): { color: string; text: string } {
6
+ switch (state?.toLowerCase()) {
7
+ case 'valid':
8
+ return { text: 'Valid', color: 'bg-green-500/20 text-green-400' }
9
+ case 'ready':
10
+ return { text: 'Ready', color: 'bg-blue-500/20 text-blue-400' }
11
+ case 'pending':
12
+ return { text: 'Pending', color: 'bg-yellow-500/20 text-yellow-400' }
13
+ case 'invalid':
14
+ return { text: 'Invalid', color: 'bg-red-500/20 text-red-400' }
15
+ case 'expired':
16
+ return { text: 'Expired', color: 'bg-red-500/20 text-red-400' }
17
+ case 'errored':
18
+ return { text: 'Errored', color: 'bg-red-500/20 text-red-400' }
19
+ default:
20
+ return { text: state || 'Unknown', color: 'bg-gray-500/20 text-gray-400' }
21
+ }
22
+ }
23
+
24
+ export function OrderRenderer({ data }: { data: any }) {
25
+ const spec = data.spec || {}
26
+ const status = data.status || {}
27
+ const conditions = status.conditions || []
28
+ const state = status.state || ''
29
+ const dnsNames = spec.dnsNames || []
30
+ const issuerRef = spec.issuerRef || {}
31
+ const authorizations = status.authorizations || []
32
+
33
+ const isError = state === 'invalid' || state === 'expired' || state === 'errored'
34
+
35
+ return (
36
+ <>
37
+ {/* Problem detection alert */}
38
+ {isError && (
39
+ <div className="mb-4 p-3 bg-red-500/10 border border-red-500/30 rounded-lg">
40
+ <div className="flex items-start gap-2">
41
+ <AlertTriangle className="w-4 h-4 text-red-400 mt-0.5 shrink-0" />
42
+ <div className="flex-1 min-w-0">
43
+ <div className="text-sm font-medium text-red-400">Order {state.charAt(0).toUpperCase() + state.slice(1)}</div>
44
+ {status.reason && (
45
+ <div className="text-xs text-red-300/80 mt-1">{status.reason}</div>
46
+ )}
47
+ </div>
48
+ </div>
49
+ </div>
50
+ )}
51
+
52
+ {/* Status */}
53
+ <Section title="Status" icon={Shield}>
54
+ <PropertyList>
55
+ <Property
56
+ label="State"
57
+ value={
58
+ <span className={clsx('px-2 py-0.5 rounded text-xs font-medium', getOrderStateBadge(state).color)}>
59
+ {getOrderStateBadge(state).text}
60
+ </span>
61
+ }
62
+ />
63
+ {status.url && <Property label="ACME URL" value={status.url} />}
64
+ </PropertyList>
65
+ </Section>
66
+
67
+ {/* Domains */}
68
+ {dnsNames.length > 0 && (
69
+ <Section title="Domains" icon={Globe}>
70
+ <div className="flex flex-wrap gap-1.5">
71
+ {dnsNames.map((name: string) => (
72
+ <span key={name} className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary font-medium">
73
+ {name}
74
+ </span>
75
+ ))}
76
+ </div>
77
+ </Section>
78
+ )}
79
+
80
+ {/* Issuer */}
81
+ {issuerRef.name && (
82
+ <Section title="Issuer" icon={Shield}>
83
+ <PropertyList>
84
+ <Property label="Name" value={issuerRef.name} />
85
+ <Property label="Kind" value={issuerRef.kind || 'ClusterIssuer'} />
86
+ {issuerRef.group && <Property label="Group" value={issuerRef.group} />}
87
+ </PropertyList>
88
+ </Section>
89
+ )}
90
+
91
+ {/* Authorizations */}
92
+ {authorizations.length > 0 && (
93
+ <Section title={`Authorizations (${authorizations.length})`} icon={ShoppingCart}>
94
+ <div className="space-y-1">
95
+ {authorizations.map((auth: any, i: number) => (
96
+ <div key={i} className="text-xs text-theme-text-secondary bg-theme-elevated/30 rounded p-2">
97
+ {auth.url || `Authorization ${i + 1}`}
98
+ </div>
99
+ ))}
100
+ </div>
101
+ </Section>
102
+ )}
103
+
104
+ {/* Conditions */}
105
+ <ConditionsSection conditions={conditions} />
106
+ </>
107
+ )
108
+ }
@@ -0,0 +1,98 @@
1
+ import { HardDrive } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
4
+
5
+ interface PVCRendererProps {
6
+ data: any
7
+ onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
8
+ }
9
+
10
+ const accessModeShorthand: Record<string, string> = {
11
+ ReadWriteOnce: 'RWO',
12
+ ReadOnlyMany: 'ROX',
13
+ ReadWriteMany: 'RWX',
14
+ ReadWriteOncePod: 'RWOP',
15
+ }
16
+
17
+ function formatAccessModes(modes: string[] | undefined): string | undefined {
18
+ if (!modes || modes.length === 0) return undefined
19
+ return modes.map(m => accessModeShorthand[m] || m).join(', ')
20
+ }
21
+
22
+ export function PVCRenderer({ data, onNavigate }: PVCRendererProps) {
23
+ const status = data.status || {}
24
+ const spec = data.spec || {}
25
+ const annotations = data.metadata?.annotations || {}
26
+ const phase = status.phase
27
+
28
+ // Problem detection
29
+ const isLost = phase === 'Lost'
30
+ const isPending = phase === 'Pending'
31
+ const hasProblems = isLost || isPending
32
+
33
+ // Provisioner info from annotations
34
+ const provisioner = annotations['volume.kubernetes.io/storage-provisioner']
35
+ const selectedNode = annotations['volume.kubernetes.io/selected-node']
36
+ const bindCompleted = annotations['pv.kubernetes.io/bind-completed']
37
+ const hasProvisionerInfo = provisioner || selectedNode || bindCompleted
38
+
39
+ return (
40
+ <>
41
+ {/* Problem alerts */}
42
+ {hasProblems && isLost && (
43
+ <AlertBanner
44
+ variant="error"
45
+ title="Issues Detected"
46
+ message="PVC has lost its bound volume"
47
+ />
48
+ )}
49
+
50
+ {hasProblems && isPending && (
51
+ <AlertBanner
52
+ variant="warning"
53
+ title="Issues Detected"
54
+ message="PVC is waiting to be bound to a volume"
55
+ />
56
+ )}
57
+
58
+ <Section title="Status" icon={HardDrive}>
59
+ <PropertyList>
60
+ <Property
61
+ label="Phase"
62
+ value={
63
+ <span className={clsx(
64
+ phase === 'Bound' && 'text-green-400',
65
+ phase === 'Pending' && 'text-yellow-400',
66
+ phase === 'Lost' && 'text-red-400',
67
+ )}>
68
+ {phase}
69
+ </span>
70
+ }
71
+ />
72
+ <Property label="Capacity" value={status.capacity?.storage} />
73
+ <Property label="Requested" value={spec.resources?.requests?.storage} />
74
+ <Property label="Storage Class" value={
75
+ spec.storageClassName ? <ResourceLink name={spec.storageClassName} kind="storageclasses" namespace="" onNavigate={onNavigate} /> : undefined
76
+ } />
77
+ <Property label="Access Modes" value={formatAccessModes(spec.accessModes)} />
78
+ <Property label="Volume Mode" value={spec.volumeMode} />
79
+ <Property label="Volume Name" value={
80
+ spec.volumeName ? <ResourceLink name={spec.volumeName} kind="persistentvolumes" namespace="" onNavigate={onNavigate} /> : undefined
81
+ } />
82
+ </PropertyList>
83
+ </Section>
84
+
85
+ {hasProvisionerInfo && (
86
+ <Section title="Provisioner Info">
87
+ <PropertyList>
88
+ <Property label="Provisioner" value={provisioner} />
89
+ <Property label="Selected Node" value={selectedNode} />
90
+ <Property label="Bind Completed" value={bindCompleted} />
91
+ </PropertyList>
92
+ </Section>
93
+ )}
94
+
95
+ <ConditionsSection conditions={status.conditions} />
96
+ </>
97
+ )
98
+ }