@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.
- package/package.json +63 -0
- package/src/components/dock/BottomDock.tsx +204 -0
- package/src/components/dock/DockContext.tsx +212 -0
- package/src/components/dock/TerminalTab.tsx +333 -0
- package/src/components/dock/index.ts +3 -0
- package/src/components/gitops/GitOpsActions.tsx +227 -0
- package/src/components/gitops/GitOpsStatusBadge.tsx +221 -0
- package/src/components/gitops/ManagedResourcesList.tsx +210 -0
- package/src/components/gitops/SyncCountdown.tsx +146 -0
- package/src/components/gitops/index.ts +4 -0
- package/src/components/logs/JsonLogLine.tsx +90 -0
- package/src/components/logs/LogCore.tsx +553 -0
- package/src/components/logs/LogToolbarSelects.tsx +74 -0
- package/src/components/logs/LogsViewer.tsx +146 -0
- package/src/components/logs/WorkloadLogsViewer.tsx +263 -0
- package/src/components/logs/index.ts +13 -0
- package/src/components/logs/useLogBuffer.ts +138 -0
- package/src/components/logs/useLogSearch.ts +151 -0
- package/src/components/logs/useLogStream.ts +61 -0
- package/src/components/resources/ResourcesView.tsx +5494 -0
- package/src/components/resources/index.ts +17 -0
- package/src/components/resources/renderers/AlertRenderer.tsx +166 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +233 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +89 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +194 -0
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +79 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +68 -0
- package/src/components/resources/renderers/CertificateRenderer.tsx +209 -0
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +123 -0
- package/src/components/resources/renderers/ChallengeRenderer.tsx +111 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +164 -0
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +140 -0
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +153 -0
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +221 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +124 -0
- package/src/components/resources/renderers/CronJobRenderer.tsx +86 -0
- package/src/components/resources/renderers/EventRenderer.tsx +175 -0
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +119 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +172 -0
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +242 -0
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +263 -0
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +68 -0
- package/src/components/resources/renderers/GatewayRenderer.tsx +218 -0
- package/src/components/resources/renderers/GenericRenderer.tsx +476 -0
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +154 -0
- package/src/components/resources/renderers/HPARenderer.tsx +164 -0
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +303 -0
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +167 -0
- package/src/components/resources/renderers/IngressClassRenderer.tsx +49 -0
- package/src/components/resources/renderers/IngressRenderer.tsx +104 -0
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +175 -0
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +176 -0
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +125 -0
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +84 -0
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +112 -0
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +288 -0
- package/src/components/resources/renderers/JobRenderer.tsx +103 -0
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +157 -0
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +188 -0
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +148 -0
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +90 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +136 -0
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +151 -0
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +70 -0
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +309 -0
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +142 -0
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +232 -0
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +97 -0
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +74 -0
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +138 -0
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +220 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +151 -0
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +352 -0
- package/src/components/resources/renderers/LeaseRenderer.tsx +93 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +208 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +282 -0
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +153 -0
- package/src/components/resources/renderers/OrderRenderer.tsx +108 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +98 -0
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +138 -0
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +126 -0
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +63 -0
- package/src/components/resources/renderers/PodRenderer.tsx +677 -0
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +50 -0
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +62 -0
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +71 -0
- package/src/components/resources/renderers/RoleBindingRenderer.tsx +97 -0
- package/src/components/resources/renderers/RoleRenderer.tsx +192 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +227 -0
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +75 -0
- package/src/components/resources/renderers/SbomReportRenderer.tsx +98 -0
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +121 -0
- package/src/components/resources/renderers/SecretRenderer.tsx +321 -0
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +111 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +81 -0
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +65 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +98 -0
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +195 -0
- package/src/components/resources/renderers/StorageClassRenderer.tsx +82 -0
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +257 -0
- package/src/components/resources/renderers/VPARenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +73 -0
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +193 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +161 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +134 -0
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +36 -0
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +347 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +156 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +202 -0
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +91 -0
- package/src/components/resources/renderers/WorkloadRenderer.tsx +311 -0
- package/src/components/resources/renderers/argo-cells.tsx +118 -0
- package/src/components/resources/renderers/certmanager-cells.tsx +164 -0
- package/src/components/resources/renderers/cnpg-cells.tsx +161 -0
- package/src/components/resources/renderers/eso-cells.tsx +115 -0
- package/src/components/resources/renderers/flux-cells.tsx +253 -0
- package/src/components/resources/renderers/index.ts +105 -0
- package/src/components/resources/renderers/istio-cells.tsx +210 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +107 -0
- package/src/components/resources/renderers/keda-cells.tsx +94 -0
- package/src/components/resources/renderers/knative-cells.tsx +396 -0
- package/src/components/resources/renderers/kyverno-cells.tsx +85 -0
- package/src/components/resources/renderers/prometheus-cells.tsx +74 -0
- package/src/components/resources/renderers/traefik-cells.tsx +120 -0
- package/src/components/resources/renderers/trivy-cells.tsx +153 -0
- package/src/components/resources/renderers/trivy-shared.tsx +67 -0
- package/src/components/resources/renderers/velero-cells.tsx +164 -0
- package/src/components/resources/resource-utils-argo.ts +155 -0
- package/src/components/resources/resource-utils-certmanager.ts +198 -0
- package/src/components/resources/resource-utils-cnpg.ts +344 -0
- package/src/components/resources/resource-utils-eso.ts +385 -0
- package/src/components/resources/resource-utils-flux.ts +186 -0
- package/src/components/resources/resource-utils-istio.ts +372 -0
- package/src/components/resources/resource-utils-karpenter.ts +150 -0
- package/src/components/resources/resource-utils-keda.ts +203 -0
- package/src/components/resources/resource-utils-knative.ts +218 -0
- package/src/components/resources/resource-utils-kyverno.ts +224 -0
- package/src/components/resources/resource-utils-prometheus.ts +159 -0
- package/src/components/resources/resource-utils-traefik.ts +180 -0
- package/src/components/resources/resource-utils-trivy.ts +146 -0
- package/src/components/resources/resource-utils-velero.ts +293 -0
- package/src/components/resources/resource-utils.ts +1558 -0
- package/src/components/shared/EditableYamlView.tsx +302 -0
- package/src/components/shared/ResourceActionsBar.tsx +924 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +557 -0
- package/src/components/shared/index.ts +3 -0
- package/src/components/timeline/index.ts +1 -0
- package/src/components/timeline/shared.tsx +614 -0
- package/src/components/topology/GroupNode.tsx +198 -0
- package/src/components/topology/K8sResourceNode.tsx +440 -0
- package/src/components/topology/TopologyControls.tsx +62 -0
- package/src/components/topology/TopologyFilterSidebar.tsx +377 -0
- package/src/components/topology/TopologyGraph.tsx +748 -0
- package/src/components/topology/TopologySearch.tsx +304 -0
- package/src/components/topology/index.ts +7 -0
- package/src/components/topology/layout.ts +734 -0
- package/src/components/topology/layout.worker.ts +272 -0
- package/src/components/topology/topology.css +129 -0
- package/src/components/ui/CodeViewer.tsx +451 -0
- package/src/components/ui/ConfirmDialog.tsx +188 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +57 -0
- package/src/components/ui/HealthRing.tsx +72 -0
- package/src/components/ui/MetricsChart.tsx +259 -0
- package/src/components/ui/ResourceBar.tsx +68 -0
- package/src/components/ui/Toast.tsx +225 -0
- package/src/components/ui/Tooltip.tsx +165 -0
- package/src/components/ui/YamlEditor.tsx +280 -0
- package/src/components/ui/drawer-components.tsx +792 -0
- package/src/components/ui/index.ts +10 -0
- package/src/components/workload/ResourceDetailDrawer.tsx +158 -0
- package/src/components/workload/WorkloadView.tsx +1109 -0
- package/src/components/workload/index.ts +2 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useAnimatedUnmount.ts +30 -0
- package/src/hooks/useKeyboardShortcuts.tsx +336 -0
- package/src/hooks/useRefreshAnimation.ts +58 -0
- package/src/index.ts +35 -0
- package/src/types/core.ts +894 -0
- package/src/types/gitops.ts +458 -0
- package/src/types/index.ts +2 -0
- package/src/utils/animation.ts +40 -0
- package/src/utils/api-resources.ts +185 -0
- package/src/utils/badge-colors.ts +399 -0
- package/src/utils/context-name.ts +72 -0
- package/src/utils/download.ts +19 -0
- package/src/utils/format.ts +233 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/log-format.ts +300 -0
- package/src/utils/navigation.ts +119 -0
- package/src/utils/resource-hierarchy.ts +630 -0
- package/src/utils/resource-icons.ts +206 -0
- package/src/vite-env.d.ts +1 -0
- 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
|
+
}
|