@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,164 @@
|
|
|
1
|
+
import { Cpu, AlertTriangle } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, ResourceLink } from '../../ui/drawer-components'
|
|
4
|
+
import { formatAge } from '../resource-utils'
|
|
5
|
+
|
|
6
|
+
interface HPARendererProps {
|
|
7
|
+
data: any
|
|
8
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Extract problems from HPA conditions
|
|
12
|
+
function getHPAProblems(data: any): string[] {
|
|
13
|
+
const problems: string[] = []
|
|
14
|
+
const conditions = data.status?.conditions || []
|
|
15
|
+
const status = data.status || {}
|
|
16
|
+
const spec = data.spec || {}
|
|
17
|
+
|
|
18
|
+
// Check conditions for issues
|
|
19
|
+
for (const cond of conditions) {
|
|
20
|
+
// AbleToScale = False means target not found or can't scale
|
|
21
|
+
if (cond.type === 'AbleToScale' && cond.status === 'False') {
|
|
22
|
+
problems.push(`Cannot scale: ${cond.reason}${cond.message ? ' - ' + cond.message : ''}`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ScalingActive = False means metrics unavailable
|
|
26
|
+
if (cond.type === 'ScalingActive' && cond.status === 'False') {
|
|
27
|
+
if (cond.reason === 'FailedGetResourceMetric') {
|
|
28
|
+
problems.push('Metrics unavailable — is metrics-server running?')
|
|
29
|
+
} else {
|
|
30
|
+
problems.push(`Scaling inactive: ${cond.reason}${cond.message ? ' - ' + cond.message : ''}`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ScalingLimited = True means at min/max bound
|
|
35
|
+
if (cond.type === 'ScalingLimited' && cond.status === 'True') {
|
|
36
|
+
if (cond.reason === 'TooFewReplicas') {
|
|
37
|
+
problems.push(`At minimum replicas (${spec.minReplicas || 1}) — cannot scale down further`)
|
|
38
|
+
} else if (cond.reason === 'TooManyReplicas') {
|
|
39
|
+
problems.push(`At maximum replicas (${spec.maxReplicas}) — cannot scale up further`)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check for desired != current (scaling in progress or stuck)
|
|
45
|
+
if (status.currentReplicas !== undefined && status.desiredReplicas !== undefined) {
|
|
46
|
+
if (status.currentReplicas !== status.desiredReplicas) {
|
|
47
|
+
const direction = status.desiredReplicas > status.currentReplicas ? 'up' : 'down'
|
|
48
|
+
problems.push(`Scaling ${direction}: ${status.currentReplicas} → ${status.desiredReplicas} replicas`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return problems
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function HPARenderer({ data, onNavigate }: HPARendererProps) {
|
|
56
|
+
const status = data.status || {}
|
|
57
|
+
const spec = data.spec || {}
|
|
58
|
+
const metrics = status.currentMetrics || []
|
|
59
|
+
|
|
60
|
+
// Check for problems
|
|
61
|
+
const problems = getHPAProblems(data)
|
|
62
|
+
const hasProblems = problems.length > 0
|
|
63
|
+
|
|
64
|
+
// Determine if these are errors (red) or warnings (yellow)
|
|
65
|
+
const hasErrors = problems.some(p =>
|
|
66
|
+
p.includes('Cannot scale') || p.includes('unavailable') || p.includes('inactive')
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
{/* Problems/warnings alert */}
|
|
72
|
+
{hasProblems && (
|
|
73
|
+
<div className={clsx(
|
|
74
|
+
'mb-4 p-3 rounded-lg border',
|
|
75
|
+
hasErrors
|
|
76
|
+
? 'bg-red-500/10 border-red-500/30'
|
|
77
|
+
: 'bg-yellow-500/10 border-yellow-500/30'
|
|
78
|
+
)}>
|
|
79
|
+
<div className="flex items-start gap-2">
|
|
80
|
+
<AlertTriangle className={clsx(
|
|
81
|
+
'w-4 h-4 mt-0.5 shrink-0',
|
|
82
|
+
hasErrors ? 'text-red-400' : 'text-yellow-400'
|
|
83
|
+
)} />
|
|
84
|
+
<div className="flex-1 min-w-0">
|
|
85
|
+
<div className={clsx(
|
|
86
|
+
'text-sm font-medium mb-1',
|
|
87
|
+
hasErrors ? 'text-red-400' : 'text-yellow-400'
|
|
88
|
+
)}>
|
|
89
|
+
{hasErrors ? 'Scaling Issues' : 'Scaling Status'}
|
|
90
|
+
</div>
|
|
91
|
+
<ul className={clsx(
|
|
92
|
+
'text-xs space-y-1',
|
|
93
|
+
hasErrors ? 'text-red-300' : 'text-yellow-300'
|
|
94
|
+
)}>
|
|
95
|
+
{problems.map((problem, i) => (
|
|
96
|
+
<li key={i} className="flex items-start gap-1.5">
|
|
97
|
+
<span className={clsx(
|
|
98
|
+
'mt-0.5',
|
|
99
|
+
hasErrors ? 'text-red-400/60' : 'text-yellow-400/60'
|
|
100
|
+
)}>•</span>
|
|
101
|
+
<span>{problem}</span>
|
|
102
|
+
</li>
|
|
103
|
+
))}
|
|
104
|
+
</ul>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
|
|
110
|
+
<Section title="Scaling" icon={Cpu}>
|
|
111
|
+
<PropertyList>
|
|
112
|
+
<Property label="Target" value={
|
|
113
|
+
spec.scaleTargetRef?.name ? (
|
|
114
|
+
<ResourceLink
|
|
115
|
+
name={spec.scaleTargetRef.name}
|
|
116
|
+
kind={(spec.scaleTargetRef.kind || 'Deployment').toLowerCase() + 's'}
|
|
117
|
+
namespace={data.metadata?.namespace || ''}
|
|
118
|
+
label={`${spec.scaleTargetRef.kind}/${spec.scaleTargetRef.name}`}
|
|
119
|
+
onNavigate={onNavigate}
|
|
120
|
+
/>
|
|
121
|
+
) : undefined
|
|
122
|
+
} />
|
|
123
|
+
<Property label="Current" value={status.currentReplicas} />
|
|
124
|
+
<Property label="Desired" value={status.desiredReplicas} />
|
|
125
|
+
<Property label="Min" value={spec.minReplicas || 1} />
|
|
126
|
+
<Property label="Max" value={spec.maxReplicas} />
|
|
127
|
+
{status.lastScaleTime && <Property label="Last Scale" value={formatAge(status.lastScaleTime)} />}
|
|
128
|
+
</PropertyList>
|
|
129
|
+
</Section>
|
|
130
|
+
|
|
131
|
+
{metrics.length > 0 && (
|
|
132
|
+
<Section title="Metrics" defaultExpanded>
|
|
133
|
+
<div className="space-y-3">
|
|
134
|
+
{metrics.map((metric: any, i: number) => {
|
|
135
|
+
const current = metric.resource?.current?.averageUtilization || metric.resource?.current?.averageValue
|
|
136
|
+
const target = spec.metrics?.[i]?.resource?.target?.averageUtilization || spec.metrics?.[i]?.resource?.target?.averageValue
|
|
137
|
+
return (
|
|
138
|
+
<div key={i} className="bg-theme-elevated/30 rounded p-2">
|
|
139
|
+
<div className="flex items-center justify-between text-sm">
|
|
140
|
+
<span className="text-theme-text-primary">{metric.resource?.name || metric.type}</span>
|
|
141
|
+
<span className="text-theme-text-secondary">{current}{typeof current === 'number' ? '%' : ''} / {target}{typeof target === 'number' ? '%' : ''}</span>
|
|
142
|
+
</div>
|
|
143
|
+
{typeof current === 'number' && typeof target === 'number' && (
|
|
144
|
+
<div className="mt-2 h-2 bg-theme-hover rounded overflow-hidden">
|
|
145
|
+
<div
|
|
146
|
+
className={clsx(
|
|
147
|
+
'h-full transition-all',
|
|
148
|
+
current > target ? 'bg-red-500' : current > target * 0.8 ? 'bg-yellow-500' : 'bg-green-500'
|
|
149
|
+
)}
|
|
150
|
+
style={{ width: `${Math.min(100, (current / target) * 100)}%` }}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
)
|
|
156
|
+
})}
|
|
157
|
+
</div>
|
|
158
|
+
</Section>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
<ConditionsSection conditions={status.conditions} />
|
|
162
|
+
</>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
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 HTTPRouteRendererProps {
|
|
7
|
+
data: any
|
|
8
|
+
onNavigate?: (ref: ResourceRef) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps) {
|
|
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
|
+
{/* Matches → backends inline */}
|
|
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
|
+
<span className="px-1.5 py-0.5 bg-blue-500/20 text-blue-400 rounded font-medium">
|
|
152
|
+
{match.method}
|
|
153
|
+
</span>
|
|
154
|
+
)}
|
|
155
|
+
{match.path && (
|
|
156
|
+
<span>
|
|
157
|
+
<span className="text-theme-text-tertiary">{match.path.type || 'PathPrefix'}:</span>{' '}
|
|
158
|
+
<span className="text-theme-text-primary">{match.path.value}</span>
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
{match.headers && match.headers.length > 0 && (
|
|
162
|
+
<span className="text-theme-text-tertiary">
|
|
163
|
+
headers: [{match.headers.map((h: any) => `${h.name}=${h.value}`).join(', ')}]
|
|
164
|
+
</span>
|
|
165
|
+
)}
|
|
166
|
+
{match.queryParams && match.queryParams.length > 0 && (
|
|
167
|
+
<span className="text-theme-text-tertiary">
|
|
168
|
+
query: [{match.queryParams.map((q: any) => `${q.name}=${q.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 inline */}
|
|
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="inline-flex items-center gap-1">
|
|
204
|
+
<span className="px-1.5 py-0.5 bg-amber-500/20 text-amber-400 rounded">
|
|
205
|
+
{filter.type}
|
|
206
|
+
</span>
|
|
207
|
+
{filter.type === 'RequestHeaderModifier' && filter.requestHeaderModifier && (
|
|
208
|
+
<span className="text-theme-text-tertiary">
|
|
209
|
+
{summarizeHeaderModifier(filter.requestHeaderModifier)}
|
|
210
|
+
</span>
|
|
211
|
+
)}
|
|
212
|
+
{filter.type === 'ResponseHeaderModifier' && filter.responseHeaderModifier && (
|
|
213
|
+
<span className="text-theme-text-tertiary">
|
|
214
|
+
{summarizeHeaderModifier(filter.responseHeaderModifier)}
|
|
215
|
+
</span>
|
|
216
|
+
)}
|
|
217
|
+
{filter.type === 'RequestRedirect' && filter.requestRedirect && (
|
|
218
|
+
<span className="text-theme-text-tertiary">
|
|
219
|
+
{filter.requestRedirect.scheme && `${filter.requestRedirect.scheme}://`}
|
|
220
|
+
{filter.requestRedirect.hostname || ''}
|
|
221
|
+
{filter.requestRedirect.port ? `:${filter.requestRedirect.port}` : ''}
|
|
222
|
+
{filter.requestRedirect.statusCode ? ` (${filter.requestRedirect.statusCode})` : ''}
|
|
223
|
+
</span>
|
|
224
|
+
)}
|
|
225
|
+
{filter.type === 'URLRewrite' && filter.urlRewrite && (
|
|
226
|
+
<span className="text-theme-text-tertiary">
|
|
227
|
+
{filter.urlRewrite.hostname && `host: ${filter.urlRewrite.hostname}`}
|
|
228
|
+
{filter.urlRewrite.path?.replacePrefixMatch && ` path: ${filter.urlRewrite.path.replacePrefixMatch}`}
|
|
229
|
+
</span>
|
|
230
|
+
)}
|
|
231
|
+
{filter.type === 'RequestMirror' && filter.requestMirror && (
|
|
232
|
+
<span className="text-theme-text-tertiary">
|
|
233
|
+
{filter.requestMirror.backendRef?.name || 'unknown'}
|
|
234
|
+
{filter.requestMirror.backendRef?.port ? `:${filter.requestMirror.backendRef.port}` : ''}
|
|
235
|
+
</span>
|
|
236
|
+
)}
|
|
237
|
+
</span>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
)
|
|
244
|
+
})}
|
|
245
|
+
</div>
|
|
246
|
+
</Section>
|
|
247
|
+
|
|
248
|
+
{/* Parent Status */}
|
|
249
|
+
{parentStatuses.length > 0 && (
|
|
250
|
+
<Section title={`Parent Status (${parentStatuses.length})`} defaultExpanded>
|
|
251
|
+
<div className="space-y-2">
|
|
252
|
+
{parentStatuses.map((parent: any, idx: number) => {
|
|
253
|
+
const ref = parent.parentRef || {}
|
|
254
|
+
const conditions = parent.conditions || []
|
|
255
|
+
const accepted = conditions.find((c: any) => c.type === 'Accepted')
|
|
256
|
+
const resolved = conditions.find((c: any) => c.type === 'ResolvedRefs')
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<div key={`${ref.namespace || ''}-${ref.name}-${idx}`} className="bg-theme-elevated/30 rounded p-2.5">
|
|
260
|
+
<div className="text-sm font-medium text-theme-text-primary mb-1.5">
|
|
261
|
+
{ref.namespace ? `${ref.namespace}/` : ''}{ref.name || 'unknown'}
|
|
262
|
+
{ref.sectionName ? ` (${ref.sectionName})` : ''}
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex flex-wrap gap-2">
|
|
265
|
+
{accepted && (
|
|
266
|
+
<span className={clsx(
|
|
267
|
+
'px-2 py-0.5 rounded text-xs font-medium',
|
|
268
|
+
accepted.status === 'True' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
|
|
269
|
+
)}>
|
|
270
|
+
{accepted.status === 'True' ? 'Accepted' : 'Not Accepted'}
|
|
271
|
+
</span>
|
|
272
|
+
)}
|
|
273
|
+
{resolved && (
|
|
274
|
+
<span className={clsx(
|
|
275
|
+
'px-2 py-0.5 rounded text-xs font-medium',
|
|
276
|
+
resolved.status === 'True' ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'
|
|
277
|
+
)}>
|
|
278
|
+
{resolved.status === 'True' ? 'Refs Resolved' : 'Unresolved Refs'}
|
|
279
|
+
</span>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
{accepted?.message && accepted.status === 'False' && (
|
|
283
|
+
<div className="text-xs text-theme-text-tertiary mt-1">{accepted.message}</div>
|
|
284
|
+
)}
|
|
285
|
+
</div>
|
|
286
|
+
)
|
|
287
|
+
})}
|
|
288
|
+
</div>
|
|
289
|
+
</Section>
|
|
290
|
+
)}
|
|
291
|
+
|
|
292
|
+
<ConditionsSection conditions={firstParentConditions} />
|
|
293
|
+
</>
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function summarizeHeaderModifier(modifier: any): string {
|
|
298
|
+
const parts: string[] = []
|
|
299
|
+
if (modifier.set?.length) parts.push(`set: ${modifier.set.map((h: any) => h.name).join(', ')}`)
|
|
300
|
+
if (modifier.add?.length) parts.push(`add: ${modifier.add.map((h: any) => h.name).join(', ')}`)
|
|
301
|
+
if (modifier.remove?.length) parts.push(`remove: ${modifier.remove.join(', ')}`)
|
|
302
|
+
return parts.join('; ')
|
|
303
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Database, AlertTriangle, CheckCircle2, Shield } 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 HelmRepositoryRendererProps {
|
|
10
|
+
data: any
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function HelmRepositoryRenderer({ data }: HelmRepositoryRendererProps) {
|
|
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
|
+
// Determine repository type
|
|
23
|
+
const isOCI = spec.type === 'oci'
|
|
24
|
+
|
|
25
|
+
// Problem detection
|
|
26
|
+
const problems: Array<{ color: 'red' | 'yellow'; message: string }> = []
|
|
27
|
+
|
|
28
|
+
if (gitOpsStatus.suspended) {
|
|
29
|
+
problems.push({ color: 'yellow', message: 'HelmRepository is suspended' })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (gitOpsStatus.health === 'Degraded' && gitOpsStatus.message) {
|
|
33
|
+
problems.push({ color: 'red', message: gitOpsStatus.message })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
{/* Problem alerts */}
|
|
39
|
+
{problems.map((problem, i) => (
|
|
40
|
+
<div
|
|
41
|
+
key={i}
|
|
42
|
+
className={clsx(
|
|
43
|
+
'mb-4 p-3 border rounded-lg',
|
|
44
|
+
problem.color === 'red'
|
|
45
|
+
? 'bg-red-500/10 border-red-500/30'
|
|
46
|
+
: 'bg-yellow-500/10 border-yellow-500/30'
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<div className="flex items-start gap-2">
|
|
50
|
+
<AlertTriangle
|
|
51
|
+
className={clsx(
|
|
52
|
+
'w-4 h-4 mt-0.5 shrink-0',
|
|
53
|
+
problem.color === 'red' ? 'text-red-400' : 'text-yellow-400'
|
|
54
|
+
)}
|
|
55
|
+
/>
|
|
56
|
+
<div className="flex-1 min-w-0">
|
|
57
|
+
<div
|
|
58
|
+
className={clsx(
|
|
59
|
+
'text-sm font-medium',
|
|
60
|
+
problem.color === 'red' ? 'text-red-400' : 'text-yellow-400'
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
{problem.color === 'red' ? 'Issue Detected' : 'Warning'}
|
|
64
|
+
</div>
|
|
65
|
+
<div
|
|
66
|
+
className={clsx(
|
|
67
|
+
'text-xs mt-1',
|
|
68
|
+
problem.color === 'red' ? 'text-red-300/80' : 'text-yellow-300/80'
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
{problem.message}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
))}
|
|
77
|
+
|
|
78
|
+
{/* Status section */}
|
|
79
|
+
<Section title="Status">
|
|
80
|
+
<div className="space-y-3">
|
|
81
|
+
<GitOpsStatusBadge status={gitOpsStatus} showHealth={false} />
|
|
82
|
+
{spec.interval && (
|
|
83
|
+
<SyncCountdown
|
|
84
|
+
interval={spec.interval}
|
|
85
|
+
lastSyncTime={status.lastHandledReconcileAt}
|
|
86
|
+
suspended={gitOpsStatus.suspended}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
</Section>
|
|
91
|
+
|
|
92
|
+
{/* Repository section */}
|
|
93
|
+
<Section title="Repository" icon={Database}>
|
|
94
|
+
<PropertyList>
|
|
95
|
+
<Property label="URL" value={spec.url} />
|
|
96
|
+
<Property
|
|
97
|
+
label="Type"
|
|
98
|
+
value={
|
|
99
|
+
<span className={clsx(
|
|
100
|
+
'px-2 py-0.5 rounded text-xs font-medium',
|
|
101
|
+
isOCI ? 'bg-purple-500/20 text-purple-400' : 'bg-blue-500/20 text-blue-400'
|
|
102
|
+
)}>
|
|
103
|
+
{isOCI ? 'OCI' : 'HTTP'}
|
|
104
|
+
</span>
|
|
105
|
+
}
|
|
106
|
+
/>
|
|
107
|
+
{spec.provider && <Property label="Provider" value={spec.provider} />}
|
|
108
|
+
</PropertyList>
|
|
109
|
+
</Section>
|
|
110
|
+
|
|
111
|
+
{/* Authentication section */}
|
|
112
|
+
{(spec.secretRef?.name || spec.certSecretRef?.name || spec.passCredentials) && (
|
|
113
|
+
<Section title="Authentication" icon={Shield} defaultExpanded={false}>
|
|
114
|
+
<PropertyList>
|
|
115
|
+
{spec.secretRef?.name && (
|
|
116
|
+
<Property label="Credentials Secret" value={spec.secretRef.name} />
|
|
117
|
+
)}
|
|
118
|
+
{spec.certSecretRef?.name && (
|
|
119
|
+
<Property label="TLS Secret" value={spec.certSecretRef.name} />
|
|
120
|
+
)}
|
|
121
|
+
{spec.passCredentials && (
|
|
122
|
+
<Property label="Pass Credentials" value="Yes" />
|
|
123
|
+
)}
|
|
124
|
+
{spec.insecure && (
|
|
125
|
+
<Property label="Insecure" value="Yes (TLS verification disabled)" />
|
|
126
|
+
)}
|
|
127
|
+
</PropertyList>
|
|
128
|
+
</Section>
|
|
129
|
+
)}
|
|
130
|
+
|
|
131
|
+
{/* Artifact/Index section */}
|
|
132
|
+
{artifact.revision && (
|
|
133
|
+
<Section title="Index" icon={CheckCircle2}>
|
|
134
|
+
<PropertyList>
|
|
135
|
+
<Property label="Revision" value={artifact.revision} />
|
|
136
|
+
<Property label="Digest" value={artifact.digest} />
|
|
137
|
+
<Property
|
|
138
|
+
label="Last Updated"
|
|
139
|
+
value={artifact.lastUpdateTime ? formatAge(artifact.lastUpdateTime) : '-'}
|
|
140
|
+
/>
|
|
141
|
+
{artifact.size && (
|
|
142
|
+
<Property label="Size" value={formatBytes(artifact.size)} />
|
|
143
|
+
)}
|
|
144
|
+
</PropertyList>
|
|
145
|
+
</Section>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{/* Additional Info */}
|
|
149
|
+
{status.observedGeneration !== undefined && (
|
|
150
|
+
<Section title="Additional Info" defaultExpanded={false}>
|
|
151
|
+
<PropertyList>
|
|
152
|
+
<Property label="Observed Generation" value={status.observedGeneration} />
|
|
153
|
+
{status.lastHandledReconcileAt && (
|
|
154
|
+
<Property
|
|
155
|
+
label="Last Reconciled"
|
|
156
|
+
value={formatAge(status.lastHandledReconcileAt)}
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
159
|
+
</PropertyList>
|
|
160
|
+
</Section>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{/* Conditions section */}
|
|
164
|
+
<ConditionsSection conditions={conditions} />
|
|
165
|
+
</>
|
|
166
|
+
)
|
|
167
|
+
}
|