@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,263 @@
1
+ import { Globe, ArrowRight, Network, Filter } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceRefBadge } from '../../ui/drawer-components'
4
+ import type { ResourceRef } from '../../../types'
5
+
6
+ interface GRPCRouteRendererProps {
7
+ data: any
8
+ onNavigate?: (ref: ResourceRef) => void
9
+ }
10
+
11
+ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps) {
12
+ const spec = data.spec || {}
13
+ const status = data.status || {}
14
+ const parentRefs = spec.parentRefs || []
15
+ const hostnames = spec.hostnames || []
16
+ const rules = spec.rules || []
17
+ const parentStatuses = status.parents || []
18
+ const routeNs = data.metadata?.namespace || ''
19
+
20
+ const notAcceptedParents = parentStatuses.filter((p: any) =>
21
+ (p.conditions || []).some((c: any) => c.type === 'Accepted' && c.status === 'False')
22
+ )
23
+ const unresolvedRefsParents = parentStatuses.filter((p: any) =>
24
+ (p.conditions || []).some((c: any) => c.type === 'ResolvedRefs' && c.status === 'False')
25
+ )
26
+
27
+ const firstParentConditions = parentStatuses.length > 0
28
+ ? parentStatuses[0].conditions
29
+ : undefined
30
+
31
+ function toGatewayRef(ref: any): ResourceRef {
32
+ return {
33
+ kind: 'Gateway',
34
+ namespace: ref.namespace || routeNs,
35
+ name: ref.name,
36
+ group: 'gateway.networking.k8s.io',
37
+ }
38
+ }
39
+
40
+ function toServiceRef(backend: any): ResourceRef {
41
+ return {
42
+ kind: 'Service',
43
+ namespace: backend.namespace || routeNs,
44
+ name: backend.name,
45
+ }
46
+ }
47
+
48
+ return (
49
+ <>
50
+ {notAcceptedParents.length > 0 && (
51
+ <AlertBanner
52
+ variant="error"
53
+ title="Route Not Accepted"
54
+ message={notAcceptedParents.map((p: any) => {
55
+ const cond = (p.conditions || []).find((c: any) => c.type === 'Accepted' && c.status === 'False')
56
+ const gwName = p.parentRef?.name || 'unknown'
57
+ return cond?.reason
58
+ ? `Gateway "${gwName}": ${cond.reason}${cond.message ? ' — ' + cond.message : ''}`
59
+ : `Gateway "${gwName}" has not accepted this route.`
60
+ }).join('; ')}
61
+ />
62
+ )}
63
+
64
+ {unresolvedRefsParents.length > 0 && (
65
+ <AlertBanner
66
+ variant="warning"
67
+ title="Unresolved References"
68
+ message="Some backend references could not be resolved. Check that the target services exist and are accessible."
69
+ />
70
+ )}
71
+
72
+ <Section title="Status" icon={Globe}>
73
+ <PropertyList>
74
+ <Property
75
+ label="Hostnames"
76
+ value={
77
+ hostnames.length > 0 ? (
78
+ <div className="flex flex-wrap gap-1">
79
+ {hostnames.map((h: string) => (
80
+ <span key={h} className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary">{h}</span>
81
+ ))}
82
+ </div>
83
+ ) : (
84
+ <span className="text-xs text-theme-text-tertiary">Any</span>
85
+ )
86
+ }
87
+ />
88
+ <Property
89
+ label="Parent Gateways"
90
+ value={
91
+ parentRefs.length > 0 ? (
92
+ <div className="flex flex-wrap gap-1">
93
+ {parentRefs.map((ref: any, i: number) => (
94
+ <ResourceRefBadge key={`${ref.namespace || ''}-${ref.name}-${i}`} resourceRef={toGatewayRef(ref)} onClick={onNavigate} />
95
+ ))}
96
+ </div>
97
+ ) : 'None'
98
+ }
99
+ />
100
+ </PropertyList>
101
+ </Section>
102
+
103
+ <Section title={`Rules (${rules.length})`} icon={Network} defaultExpanded>
104
+ <div className="space-y-1.5">
105
+ {rules.map((rule: any, ruleIdx: number) => {
106
+ const matches = rule.matches || []
107
+ const backendRefs = rule.backendRefs || []
108
+ const filters = rule.filters || []
109
+ const totalWeight = backendRefs.reduce((sum: number, b: any) => sum + (b.weight ?? 1), 0)
110
+ const hasWeights = backendRefs.length > 1 && backendRefs.some((b: any) => b.weight !== undefined)
111
+ const multiBackend = backendRefs.length > 1
112
+
113
+ return (
114
+ <div key={ruleIdx} className="bg-theme-elevated/30 rounded p-2.5">
115
+ <div className="text-xs font-medium text-theme-text-tertiary mb-1.5">
116
+ Rule {ruleIdx + 1}
117
+ </div>
118
+
119
+ {/* gRPC matches → backends */}
120
+ <div className="space-y-1">
121
+ {matches.length === 0 && backendRefs.length === 0 && (
122
+ <div className="text-xs text-theme-text-secondary italic">Match all</div>
123
+ )}
124
+ {matches.length === 0 && backendRefs.length > 0 && !multiBackend && (
125
+ <div className="text-xs text-theme-text-secondary flex flex-wrap items-center gap-1.5">
126
+ <span className="italic text-theme-text-tertiary">all</span>
127
+ <ArrowRight className="w-3 h-3 text-theme-text-tertiary shrink-0" />
128
+ <ResourceRefBadge resourceRef={toServiceRef(backendRefs[0])} onClick={onNavigate} />
129
+ {backendRefs[0].port && <span className="text-theme-text-tertiary">:{backendRefs[0].port}</span>}
130
+ </div>
131
+ )}
132
+ {matches.length === 0 && multiBackend && (
133
+ <>
134
+ <div className="text-xs text-theme-text-secondary italic">all</div>
135
+ {backendRefs.map((b: any, bi: number) => {
136
+ const pct = hasWeights ? Math.round(((b.weight ?? 1) / totalWeight) * 100) : null
137
+ return (
138
+ <div key={bi} className="text-xs text-theme-text-secondary flex items-center gap-1.5 pl-1">
139
+ <ArrowRight className="w-3 h-3 text-theme-text-tertiary shrink-0" />
140
+ <ResourceRefBadge resourceRef={toServiceRef(b)} onClick={onNavigate} />
141
+ {b.port && <span className="text-theme-text-tertiary">:{b.port}</span>}
142
+ {pct !== null && <span className="text-theme-text-tertiary text-[10px]">{pct}%</span>}
143
+ </div>
144
+ )
145
+ })}
146
+ </>
147
+ )}
148
+ {matches.map((match: any, matchIdx: number) => (
149
+ <div key={matchIdx} className="text-xs text-theme-text-secondary flex flex-wrap items-center gap-1.5">
150
+ {match.method && (
151
+ <>
152
+ {match.method.type && match.method.type !== 'Exact' && (
153
+ <span className="px-1.5 py-0.5 bg-amber-500/20 text-amber-400 rounded text-[10px]">{match.method.type}</span>
154
+ )}
155
+ {match.method.service && (
156
+ <span>
157
+ <span className="text-theme-text-primary font-medium">{match.method.service}</span>
158
+ {match.method.method && <span className="text-theme-text-tertiary">/{match.method.method}</span>}
159
+ </span>
160
+ )}
161
+ {!match.method.service && match.method.method && (
162
+ <span><span className="text-theme-text-tertiary">*/</span><span className="text-theme-text-primary">{match.method.method}</span></span>
163
+ )}
164
+ </>
165
+ )}
166
+ {match.headers && match.headers.length > 0 && (
167
+ <span className="text-theme-text-tertiary">
168
+ headers: [{match.headers.map((h: any) => `${h.name}=${h.value}`).join(', ')}]
169
+ </span>
170
+ )}
171
+ {/* Single backend inline on first match */}
172
+ {matchIdx === 0 && backendRefs.length === 1 && (
173
+ <>
174
+ <ArrowRight className="w-3 h-3 text-theme-text-tertiary shrink-0" />
175
+ <ResourceRefBadge resourceRef={toServiceRef(backendRefs[0])} onClick={onNavigate} />
176
+ {backendRefs[0].port && <span className="text-theme-text-tertiary">:{backendRefs[0].port}</span>}
177
+ </>
178
+ )}
179
+ </div>
180
+ ))}
181
+ {/* Multiple backends as aligned column below matches */}
182
+ {matches.length > 0 && multiBackend && (
183
+ backendRefs.map((b: any, bi: number) => {
184
+ const pct = hasWeights ? Math.round(((b.weight ?? 1) / totalWeight) * 100) : null
185
+ return (
186
+ <div key={bi} className="text-xs text-theme-text-secondary flex items-center gap-1.5 pl-1">
187
+ <ArrowRight className="w-3 h-3 text-theme-text-tertiary shrink-0" />
188
+ <ResourceRefBadge resourceRef={toServiceRef(b)} onClick={onNavigate} />
189
+ {b.port && <span className="text-theme-text-tertiary">:{b.port}</span>}
190
+ {pct !== null && <span className="text-theme-text-tertiary text-[10px]">{pct}%</span>}
191
+ </div>
192
+ )
193
+ })
194
+ )}
195
+ </div>
196
+
197
+ {/* Filters */}
198
+ {filters.length > 0 && (
199
+ <div className="mt-1.5">
200
+ <div className="text-xs text-theme-text-secondary flex flex-wrap items-center gap-1.5">
201
+ <Filter className="w-3 h-3 text-theme-text-tertiary" />
202
+ {filters.map((filter: any, filterIdx: number) => (
203
+ <span key={filterIdx} className="px-1.5 py-0.5 bg-amber-500/20 text-amber-400 rounded">
204
+ {filter.type}
205
+ </span>
206
+ ))}
207
+ </div>
208
+ </div>
209
+ )}
210
+ </div>
211
+ )
212
+ })}
213
+ </div>
214
+ </Section>
215
+
216
+ {/* Parent Status */}
217
+ {parentStatuses.length > 0 && (
218
+ <Section title={`Parent Status (${parentStatuses.length})`} defaultExpanded>
219
+ <div className="space-y-2">
220
+ {parentStatuses.map((parent: any, idx: number) => {
221
+ const ref = parent.parentRef || {}
222
+ const conditions = parent.conditions || []
223
+ const accepted = conditions.find((c: any) => c.type === 'Accepted')
224
+ const resolved = conditions.find((c: any) => c.type === 'ResolvedRefs')
225
+
226
+ return (
227
+ <div key={`${ref.namespace || ''}-${ref.name}-${idx}`} className="bg-theme-elevated/30 rounded p-2.5">
228
+ <div className="text-sm font-medium text-theme-text-primary mb-1.5">
229
+ {ref.namespace ? `${ref.namespace}/` : ''}{ref.name || 'unknown'}
230
+ {ref.sectionName ? ` (${ref.sectionName})` : ''}
231
+ </div>
232
+ <div className="flex flex-wrap gap-2">
233
+ {accepted && (
234
+ <span className={clsx(
235
+ 'px-2 py-0.5 rounded text-xs font-medium',
236
+ accepted.status === 'True' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
237
+ )}>
238
+ {accepted.status === 'True' ? 'Accepted' : 'Not Accepted'}
239
+ </span>
240
+ )}
241
+ {resolved && (
242
+ <span className={clsx(
243
+ 'px-2 py-0.5 rounded text-xs font-medium',
244
+ resolved.status === 'True' ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'
245
+ )}>
246
+ {resolved.status === 'True' ? 'Refs Resolved' : 'Unresolved Refs'}
247
+ </span>
248
+ )}
249
+ </div>
250
+ {accepted?.message && accepted.status === 'False' && (
251
+ <div className="text-xs text-theme-text-tertiary mt-1">{accepted.message}</div>
252
+ )}
253
+ </div>
254
+ )
255
+ })}
256
+ </div>
257
+ </Section>
258
+ )}
259
+
260
+ <ConditionsSection conditions={firstParentConditions} />
261
+ </>
262
+ )
263
+ }
@@ -0,0 +1,68 @@
1
+ import { Cpu } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
4
+
5
+ interface GatewayClassRendererProps {
6
+ data: any
7
+ }
8
+
9
+ export function GatewayClassRenderer({ data }: GatewayClassRendererProps) {
10
+ const spec = data.spec || {}
11
+ const status = data.status || {}
12
+ const conditions = status.conditions || []
13
+
14
+ const acceptedCond = conditions.find((c: any) => c.type === 'Accepted')
15
+ const isAccepted = acceptedCond?.status === 'True'
16
+ const isNotAccepted = acceptedCond?.status === 'False'
17
+
18
+ return (
19
+ <>
20
+ {/* Problem detection alert */}
21
+ {isNotAccepted && (
22
+ <AlertBanner
23
+ variant="error"
24
+ title="GatewayClass Not Accepted"
25
+ message={<>{acceptedCond.reason && <span className="font-medium">{acceptedCond.reason}: </span>}{acceptedCond.message || 'The gateway class has not been accepted by the controller.'}</>}
26
+ />
27
+ )}
28
+
29
+ {/* Controller section */}
30
+ <Section title="Gateway Class" icon={Cpu}>
31
+ <PropertyList>
32
+ <Property label="Controller" value={spec.controllerName} />
33
+ {spec.description && <Property label="Description" value={spec.description} />}
34
+ <Property
35
+ label="Accepted"
36
+ value={
37
+ <span className={clsx(
38
+ 'px-2 py-0.5 rounded text-xs font-medium',
39
+ isAccepted
40
+ ? 'bg-green-500/20 text-green-400'
41
+ : isNotAccepted
42
+ ? 'bg-red-500/20 text-red-400'
43
+ : 'bg-gray-500/20 text-gray-400'
44
+ )}>
45
+ {isAccepted ? 'True' : isNotAccepted ? 'False' : 'Unknown'}
46
+ </span>
47
+ }
48
+ />
49
+ </PropertyList>
50
+ </Section>
51
+
52
+ {/* Parameter references (if any) */}
53
+ {spec.parametersRef && (
54
+ <Section title="Parameters Reference">
55
+ <PropertyList>
56
+ <Property label="Group" value={spec.parametersRef.group} />
57
+ <Property label="Kind" value={spec.parametersRef.kind} />
58
+ <Property label="Name" value={spec.parametersRef.name} />
59
+ {spec.parametersRef.namespace && <Property label="Namespace" value={spec.parametersRef.namespace} />}
60
+ </PropertyList>
61
+ </Section>
62
+ )}
63
+
64
+ {/* Conditions */}
65
+ <ConditionsSection conditions={conditions} />
66
+ </>
67
+ )
68
+ }
@@ -0,0 +1,218 @@
1
+ import { Globe, Radio, Lock } from 'lucide-react'
2
+ import { clsx } from 'clsx'
3
+ import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
4
+
5
+ const protocolColors: Record<string, string> = {
6
+ HTTP: 'bg-blue-500/20 text-blue-400',
7
+ HTTPS: 'bg-green-500/20 text-green-400',
8
+ TLS: 'bg-green-500/20 text-green-400',
9
+ TCP: 'bg-orange-500/20 text-orange-400',
10
+ UDP: 'bg-purple-500/20 text-purple-400',
11
+ }
12
+
13
+ interface GatewayRendererProps {
14
+ data: any
15
+ onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
16
+ }
17
+
18
+ export function GatewayRenderer({ data, onNavigate }: GatewayRendererProps) {
19
+ const spec = data.spec || {}
20
+ const status = data.status || {}
21
+ const conditions = status.conditions || []
22
+ const listeners = spec.listeners || []
23
+ const statusListeners = status.listeners || []
24
+ const statusAddresses = status.addresses || []
25
+ const specAddresses = spec.addresses || []
26
+
27
+ const acceptedCond = conditions.find((c: any) => c.type === 'Accepted')
28
+ const programmedCond = conditions.find((c: any) => c.type === 'Programmed')
29
+ const isAccepted = acceptedCond?.status === 'True'
30
+ const isNotAccepted = acceptedCond?.status === 'False'
31
+ const isProgrammed = programmedCond?.status === 'True'
32
+ const isNotProgrammed = programmedCond?.status === 'False'
33
+
34
+ // Merge spec and status addresses for display
35
+ const allAddresses = statusAddresses.length > 0 ? statusAddresses : specAddresses
36
+
37
+ // Helper to find status for a listener by name
38
+ function getListenerStatus(name: string) {
39
+ return statusListeners.find((sl: any) => sl.name === name)
40
+ }
41
+
42
+ return (
43
+ <>
44
+ {/* Problem detection alerts */}
45
+ {isNotAccepted && (
46
+ <AlertBanner
47
+ variant="error"
48
+ title="Gateway Not Accepted"
49
+ message={<>{acceptedCond.reason && <span className="font-medium">{acceptedCond.reason}: </span>}{acceptedCond.message || 'The gateway has not been accepted by the controller.'}</>}
50
+ />
51
+ )}
52
+
53
+ {isNotProgrammed && (
54
+ <AlertBanner
55
+ variant="warning"
56
+ title="Gateway Not Programmed"
57
+ message={<>{programmedCond.reason && <span className="font-medium">{programmedCond.reason}: </span>}{programmedCond.message || 'The gateway configuration has not been programmed into the data plane.'}</>}
58
+ />
59
+ )}
60
+
61
+ {/* Status section */}
62
+ <Section title="Gateway" icon={Globe}>
63
+ <PropertyList>
64
+ <Property label="Gateway Class" value={
65
+ spec.gatewayClassName ? <ResourceLink name={spec.gatewayClassName} kind="gatewayclasses" onNavigate={onNavigate} /> : undefined
66
+ } />
67
+ <Property
68
+ label="Accepted"
69
+ value={
70
+ <span className={clsx(
71
+ 'px-2 py-0.5 rounded text-xs font-medium',
72
+ isAccepted
73
+ ? 'bg-green-500/20 text-green-400'
74
+ : isNotAccepted
75
+ ? 'bg-red-500/20 text-red-400'
76
+ : 'bg-gray-500/20 text-gray-400'
77
+ )}>
78
+ {isAccepted ? 'True' : isNotAccepted ? 'False' : 'Unknown'}
79
+ </span>
80
+ }
81
+ />
82
+ <Property
83
+ label="Programmed"
84
+ value={
85
+ <span className={clsx(
86
+ 'px-2 py-0.5 rounded text-xs font-medium',
87
+ isProgrammed
88
+ ? 'bg-green-500/20 text-green-400'
89
+ : isNotProgrammed
90
+ ? 'bg-red-500/20 text-red-400'
91
+ : 'bg-gray-500/20 text-gray-400'
92
+ )}>
93
+ {isProgrammed ? 'True' : isNotProgrammed ? 'False' : 'Unknown'}
94
+ </span>
95
+ }
96
+ />
97
+ </PropertyList>
98
+ </Section>
99
+
100
+ {/* Addresses section */}
101
+ {allAddresses.length > 0 && (
102
+ <Section title="Addresses" defaultExpanded>
103
+ <div className="space-y-2">
104
+ {allAddresses.map((addr: any, i: number) => (
105
+ <div key={`${addr.type}-${addr.value}-${i}`} className="flex items-center gap-2 text-sm">
106
+ <span className="text-theme-text-tertiary w-28 shrink-0">{addr.type || 'Unknown'}</span>
107
+ <span className="text-theme-text-primary break-all">{addr.value}</span>
108
+ </div>
109
+ ))}
110
+ </div>
111
+ </Section>
112
+ )}
113
+
114
+ {/* Listeners section */}
115
+ <Section title={`Listeners (${listeners.length})`} icon={Radio} defaultExpanded>
116
+ <div className="space-y-3">
117
+ {listeners.map((listener: any) => {
118
+ const listenerStatus = getListenerStatus(listener.name)
119
+ const listenerConditions = listenerStatus?.conditions || []
120
+ const listenerAccepted = listenerConditions.find((c: any) => c.type === 'Accepted')
121
+ const listenerConflicted = listenerConditions.find((c: any) => c.type === 'Conflicted')
122
+ const listenerResolvedRefs = listenerConditions.find((c: any) => c.type === 'ResolvedRefs')
123
+ const isListenerAccepted = listenerAccepted?.status === 'True'
124
+ const isListenerNotAccepted = listenerAccepted?.status === 'False'
125
+ const isConflicted = listenerConflicted?.status === 'True'
126
+ const hasUnresolvedRefs = listenerResolvedRefs?.status === 'False'
127
+ const isHTTPS = listener.protocol === 'HTTPS' || listener.protocol === 'TLS'
128
+
129
+ return (
130
+ <div key={listener.name} className="bg-theme-elevated/30 rounded p-3">
131
+ <div className="flex items-center justify-between mb-2">
132
+ <div className="flex items-center gap-2">
133
+ {isHTTPS && <Lock className="w-3.5 h-3.5 text-green-400" />}
134
+ <span className="text-sm font-medium text-theme-text-primary">{listener.name}</span>
135
+ <span className={clsx(
136
+ 'px-1.5 py-0.5 rounded text-[10px] font-medium',
137
+ protocolColors[listener.protocol] || 'bg-gray-500/20 text-gray-400'
138
+ )}>
139
+ {listener.protocol}:{listener.port}
140
+ </span>
141
+ </div>
142
+ <div className="flex items-center gap-1">
143
+ {isConflicted && (
144
+ <span className="px-1.5 py-0.5 rounded text-[10px] font-medium bg-red-500/20 text-red-400">
145
+ Conflicted
146
+ </span>
147
+ )}
148
+ {hasUnresolvedRefs && (
149
+ <span className="px-1.5 py-0.5 rounded text-[10px] font-medium bg-yellow-500/20 text-yellow-400">
150
+ Unresolved Refs
151
+ </span>
152
+ )}
153
+ {listenerAccepted && (
154
+ <span className={clsx(
155
+ 'w-4 h-4 rounded-full flex items-center justify-center text-xs shrink-0',
156
+ isListenerAccepted
157
+ ? 'bg-green-500/20 text-green-400'
158
+ : isListenerNotAccepted
159
+ ? 'bg-red-500/20 text-red-400'
160
+ : 'bg-gray-500/20 text-gray-400'
161
+ )}>
162
+ {isListenerAccepted ? '\u2713' : isListenerNotAccepted ? '\u2717' : '?'}
163
+ </span>
164
+ )}
165
+ </div>
166
+ </div>
167
+
168
+ <div className="space-y-1 text-xs text-theme-text-secondary">
169
+ {listener.hostname && (
170
+ <div>
171
+ <span className="text-theme-text-tertiary">Hostname: </span>
172
+ <span>{listener.hostname}</span>
173
+ </div>
174
+ )}
175
+
176
+ {isHTTPS && listener.tls && (
177
+ <div>
178
+ <span className="text-theme-text-tertiary">TLS Mode: </span>
179
+ <span>{listener.tls.mode || 'Terminate'}</span>
180
+ {listener.tls.certificateRefs?.length > 0 && (
181
+ <span className="ml-2">
182
+ <span className="text-theme-text-tertiary">Certs: </span>
183
+ {listener.tls.certificateRefs.map((ref: any, ci: number) => (
184
+ <span key={ci}>
185
+ {ci > 0 && ', '}
186
+ <ResourceLink name={ref.name} kind="secrets" namespace={ref.namespace || data.metadata?.namespace || ''} onNavigate={onNavigate} />
187
+ </span>
188
+ ))}
189
+ </span>
190
+ )}
191
+ </div>
192
+ )}
193
+
194
+ {listener.allowedRoutes && (
195
+ <div>
196
+ <span className="text-theme-text-tertiary">Allowed Routes: </span>
197
+ <span>{listener.allowedRoutes.namespaces?.from || 'Same'}</span>
198
+ </div>
199
+ )}
200
+
201
+ {listenerStatus && (
202
+ <div>
203
+ <span className="text-theme-text-tertiary">Attached Routes: </span>
204
+ <span className="text-theme-text-primary font-medium">{listenerStatus.attachedRoutes ?? 0}</span>
205
+ </div>
206
+ )}
207
+ </div>
208
+ </div>
209
+ )
210
+ })}
211
+ </div>
212
+ </Section>
213
+
214
+ {/* Conditions */}
215
+ <ConditionsSection conditions={conditions} />
216
+ </>
217
+ )
218
+ }