@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,194 @@
|
|
|
1
|
+
import { Database, HardDrive, Activity, Clock, Shield } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
3
|
+
import {
|
|
4
|
+
getCNPGClusterInstances,
|
|
5
|
+
getCNPGClusterPrimary,
|
|
6
|
+
getCNPGClusterPhase,
|
|
7
|
+
getCNPGClusterImage,
|
|
8
|
+
getCNPGClusterStorage,
|
|
9
|
+
getCNPGClusterStorageClass,
|
|
10
|
+
getCNPGClusterWALStorage,
|
|
11
|
+
getCNPGClusterBootstrapMethod,
|
|
12
|
+
getCNPGClusterUpdateStrategy,
|
|
13
|
+
getCNPGClusterBackupConfig,
|
|
14
|
+
getCNPGClusterMonitoring,
|
|
15
|
+
getCNPGClusterIsReplica,
|
|
16
|
+
getCNPGClusterReplicaSource,
|
|
17
|
+
getCNPGClusterInstanceNames,
|
|
18
|
+
getCNPGClusterPostgresParams,
|
|
19
|
+
} from '../resource-utils-cnpg'
|
|
20
|
+
|
|
21
|
+
interface CNPGClusterRendererProps {
|
|
22
|
+
data: any
|
|
23
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function CNPGClusterRenderer({ data, onNavigate }: CNPGClusterRendererProps) {
|
|
27
|
+
const conditions = data.status?.conditions || []
|
|
28
|
+
const instances = data.spec?.instances ?? 0
|
|
29
|
+
const readyInstances = data.status?.readyInstances ?? 0
|
|
30
|
+
const phase = getCNPGClusterPhase(data)
|
|
31
|
+
const backupConfig = getCNPGClusterBackupConfig(data)
|
|
32
|
+
const monitoring = getCNPGClusterMonitoring(data)
|
|
33
|
+
const isReplica = getCNPGClusterIsReplica(data)
|
|
34
|
+
const walStorage = getCNPGClusterWALStorage(data)
|
|
35
|
+
const postgresParams = getCNPGClusterPostgresParams(data)
|
|
36
|
+
const instanceNames = getCNPGClusterInstanceNames(data)
|
|
37
|
+
const bootstrapMethod = getCNPGClusterBootstrapMethod(data)
|
|
38
|
+
|
|
39
|
+
// Problem detection
|
|
40
|
+
const isDegraded = instances > 0 && readyInstances < instances
|
|
41
|
+
const isFailover = phase.toLowerCase().includes('failing over')
|
|
42
|
+
const isSwitchover = phase.toLowerCase().includes('switchover')
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<>
|
|
46
|
+
{/* Problem alerts */}
|
|
47
|
+
{isDegraded && (
|
|
48
|
+
<AlertBanner
|
|
49
|
+
variant="warning"
|
|
50
|
+
title="Degraded Cluster"
|
|
51
|
+
message={`Only ${readyInstances} of ${instances} instances are ready.`}
|
|
52
|
+
/>
|
|
53
|
+
)}
|
|
54
|
+
{isFailover && (
|
|
55
|
+
<AlertBanner
|
|
56
|
+
variant="error"
|
|
57
|
+
title="Failover in Progress"
|
|
58
|
+
message={`Cluster is performing a failover. Current phase: ${phase}`}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
{isSwitchover && (
|
|
62
|
+
<AlertBanner
|
|
63
|
+
variant="warning"
|
|
64
|
+
title="Switchover in Progress"
|
|
65
|
+
message={`Cluster is performing a switchover. Current phase: ${phase}`}
|
|
66
|
+
/>
|
|
67
|
+
)}
|
|
68
|
+
|
|
69
|
+
{/* Cluster Overview */}
|
|
70
|
+
<Section title="Cluster Overview" icon={Database} defaultExpanded>
|
|
71
|
+
<PropertyList>
|
|
72
|
+
<Property label="Phase" value={phase} />
|
|
73
|
+
<Property label="Instances" value={getCNPGClusterInstances(data)} />
|
|
74
|
+
<Property label="Current Primary" value={getCNPGClusterPrimary(data)} />
|
|
75
|
+
<Property label="Image" value={getCNPGClusterImage(data)} />
|
|
76
|
+
<Property label="Update Strategy" value={getCNPGClusterUpdateStrategy(data)} />
|
|
77
|
+
</PropertyList>
|
|
78
|
+
{instanceNames.length > 0 && (
|
|
79
|
+
<div className="mt-2 pt-2 border-t border-theme-border">
|
|
80
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1">Instance Nodes</div>
|
|
81
|
+
<div className="flex flex-wrap gap-1">
|
|
82
|
+
{instanceNames.map((name: string) => (
|
|
83
|
+
<span
|
|
84
|
+
key={name}
|
|
85
|
+
className="px-1.5 py-0.5 bg-theme-hover rounded text-xs text-theme-text-secondary font-mono"
|
|
86
|
+
>
|
|
87
|
+
{name}
|
|
88
|
+
</span>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</Section>
|
|
94
|
+
|
|
95
|
+
{/* Storage */}
|
|
96
|
+
<Section title="Storage" icon={HardDrive} defaultExpanded>
|
|
97
|
+
<PropertyList>
|
|
98
|
+
<Property label="Data Size" value={getCNPGClusterStorage(data)} />
|
|
99
|
+
<Property label="Storage Class" value={getCNPGClusterStorageClass(data)} />
|
|
100
|
+
</PropertyList>
|
|
101
|
+
{walStorage && (
|
|
102
|
+
<div className="mt-2 pt-2 border-t border-theme-border">
|
|
103
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1">WAL Storage</div>
|
|
104
|
+
<PropertyList>
|
|
105
|
+
{walStorage.size && <Property label="Size" value={walStorage.size} />}
|
|
106
|
+
{walStorage.storageClass && <Property label="Storage Class" value={walStorage.storageClass} />}
|
|
107
|
+
</PropertyList>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
</Section>
|
|
111
|
+
|
|
112
|
+
{/* Bootstrap - only show if method is interesting */}
|
|
113
|
+
{bootstrapMethod !== '-' && bootstrapMethod !== 'initdb' && (
|
|
114
|
+
<Section title="Bootstrap" icon={Activity} defaultExpanded>
|
|
115
|
+
<PropertyList>
|
|
116
|
+
<Property label="Method" value={bootstrapMethod} />
|
|
117
|
+
</PropertyList>
|
|
118
|
+
</Section>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{/* Backup */}
|
|
122
|
+
{backupConfig.configured && (
|
|
123
|
+
<Section title="Backup" icon={Clock} defaultExpanded>
|
|
124
|
+
<PropertyList>
|
|
125
|
+
{backupConfig.destinationPath && (
|
|
126
|
+
<Property label="Destination" value={backupConfig.destinationPath} />
|
|
127
|
+
)}
|
|
128
|
+
{backupConfig.retentionPolicy && (
|
|
129
|
+
<Property label="Retention" value={backupConfig.retentionPolicy} />
|
|
130
|
+
)}
|
|
131
|
+
{backupConfig.lastSuccessfulBackup && (
|
|
132
|
+
<Property label="Last Successful" value={backupConfig.lastSuccessfulBackup} />
|
|
133
|
+
)}
|
|
134
|
+
{backupConfig.firstRecoverabilityPoint && (
|
|
135
|
+
<Property label="First Recoverability" value={backupConfig.firstRecoverabilityPoint} />
|
|
136
|
+
)}
|
|
137
|
+
</PropertyList>
|
|
138
|
+
</Section>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
{/* Monitoring */}
|
|
142
|
+
{(monitoring.podMonitorEnabled || (monitoring.customQueriesConfigMap && monitoring.customQueriesConfigMap.length > 0)) && (
|
|
143
|
+
<Section title="Monitoring" icon={Activity} defaultExpanded>
|
|
144
|
+
<PropertyList>
|
|
145
|
+
<Property label="Pod Monitor" value={monitoring.podMonitorEnabled ? 'Enabled' : 'Disabled'} />
|
|
146
|
+
</PropertyList>
|
|
147
|
+
{monitoring.customQueriesConfigMap && monitoring.customQueriesConfigMap.length > 0 && (
|
|
148
|
+
<div className="mt-2 pt-2 border-t border-theme-border">
|
|
149
|
+
<div className="text-xs font-medium text-theme-text-secondary uppercase tracking-wider mb-1">Custom Queries</div>
|
|
150
|
+
<div className="flex flex-wrap gap-1">
|
|
151
|
+
{monitoring.customQueriesConfigMap.map((name: string) => (
|
|
152
|
+
<ResourceLink
|
|
153
|
+
key={name}
|
|
154
|
+
name={name}
|
|
155
|
+
kind="configmaps"
|
|
156
|
+
namespace={data.metadata?.namespace || ''}
|
|
157
|
+
onNavigate={onNavigate}
|
|
158
|
+
/>
|
|
159
|
+
))}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
163
|
+
</Section>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
{/* Replication - only if this is a replica cluster */}
|
|
167
|
+
{isReplica && (
|
|
168
|
+
<Section title="Replication" icon={Shield} defaultExpanded>
|
|
169
|
+
<PropertyList>
|
|
170
|
+
<Property label="Role" value="Replica" />
|
|
171
|
+
<Property label="Source" value={getCNPGClusterReplicaSource(data)} />
|
|
172
|
+
</PropertyList>
|
|
173
|
+
</Section>
|
|
174
|
+
)}
|
|
175
|
+
|
|
176
|
+
{/* PostgreSQL Parameters */}
|
|
177
|
+
{Object.keys(postgresParams).length > 0 && (
|
|
178
|
+
<Section title="PostgreSQL Parameters" defaultExpanded={false}>
|
|
179
|
+
<div className="space-y-0.5">
|
|
180
|
+
{Object.entries(postgresParams).map(([key, value]) => (
|
|
181
|
+
<div key={key} className="flex items-center gap-2 text-xs">
|
|
182
|
+
<span className="text-theme-text-secondary font-mono shrink-0">{key}</span>
|
|
183
|
+
<span className="text-theme-text-tertiary">=</span>
|
|
184
|
+
<span className="text-theme-text-primary font-mono break-all">{value}</span>
|
|
185
|
+
</div>
|
|
186
|
+
))}
|
|
187
|
+
</div>
|
|
188
|
+
</Section>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
<ConditionsSection conditions={conditions} />
|
|
192
|
+
</>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Users, Database } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
3
|
+
import {
|
|
4
|
+
getCNPGPoolerCluster,
|
|
5
|
+
getCNPGPoolerType,
|
|
6
|
+
getCNPGPoolerMode,
|
|
7
|
+
getCNPGPoolerInstances,
|
|
8
|
+
getCNPGPoolerParameters,
|
|
9
|
+
} from '../resource-utils-cnpg'
|
|
10
|
+
|
|
11
|
+
interface CNPGPoolerRendererProps {
|
|
12
|
+
data: any
|
|
13
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function CNPGPoolerRenderer({ data, onNavigate }: CNPGPoolerRendererProps) {
|
|
17
|
+
const desired = data.spec?.instances ?? 0
|
|
18
|
+
const ready = data.status?.instances ?? 0
|
|
19
|
+
const isDegraded = desired > 0 && ready < desired
|
|
20
|
+
const clusterName = getCNPGPoolerCluster(data)
|
|
21
|
+
const parameters = getCNPGPoolerParameters(data)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
{/* Problem alerts */}
|
|
26
|
+
{isDegraded && (
|
|
27
|
+
<AlertBanner
|
|
28
|
+
variant="warning"
|
|
29
|
+
title="Pooler Degraded"
|
|
30
|
+
message={`Only ${ready} of ${desired} pooler instances are ready.`}
|
|
31
|
+
/>
|
|
32
|
+
)}
|
|
33
|
+
|
|
34
|
+
{/* Pooler Configuration */}
|
|
35
|
+
<Section title="Pooler Configuration" icon={Users} defaultExpanded>
|
|
36
|
+
<PropertyList>
|
|
37
|
+
<Property label="Type" value={getCNPGPoolerType(data)} />
|
|
38
|
+
<Property label="Pool Mode" value={getCNPGPoolerMode(data)} />
|
|
39
|
+
<Property label="Instances" value={getCNPGPoolerInstances(data)} />
|
|
40
|
+
</PropertyList>
|
|
41
|
+
</Section>
|
|
42
|
+
|
|
43
|
+
{/* Cluster Reference */}
|
|
44
|
+
<Section title="Cluster" icon={Database} defaultExpanded>
|
|
45
|
+
<PropertyList>
|
|
46
|
+
<Property label="Cluster" value={(() => {
|
|
47
|
+
if (clusterName && clusterName !== '-') {
|
|
48
|
+
return (
|
|
49
|
+
<ResourceLink
|
|
50
|
+
name={clusterName}
|
|
51
|
+
kind="clusters"
|
|
52
|
+
namespace={data.metadata?.namespace || ''}
|
|
53
|
+
group="postgresql.cnpg.io"
|
|
54
|
+
onNavigate={onNavigate}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
return clusterName
|
|
59
|
+
})()} />
|
|
60
|
+
</PropertyList>
|
|
61
|
+
</Section>
|
|
62
|
+
|
|
63
|
+
{/* PgBouncer Parameters */}
|
|
64
|
+
{Object.keys(parameters).length > 0 && (
|
|
65
|
+
<Section title="PgBouncer Parameters" defaultExpanded>
|
|
66
|
+
<div className="space-y-0.5">
|
|
67
|
+
{Object.entries(parameters).map(([key, value]) => (
|
|
68
|
+
<div key={key} className="flex items-center gap-2 text-xs">
|
|
69
|
+
<span className="text-theme-text-secondary font-mono shrink-0">{key}</span>
|
|
70
|
+
<span className="text-theme-text-tertiary">=</span>
|
|
71
|
+
<span className="text-theme-text-primary font-mono break-all">{value}</span>
|
|
72
|
+
</div>
|
|
73
|
+
))}
|
|
74
|
+
</div>
|
|
75
|
+
</Section>
|
|
76
|
+
)}
|
|
77
|
+
</>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Clock, Database } from 'lucide-react'
|
|
2
|
+
import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
3
|
+
import {
|
|
4
|
+
getCNPGScheduledBackupCluster,
|
|
5
|
+
getCNPGScheduleCron,
|
|
6
|
+
getCNPGScheduledBackupMethod,
|
|
7
|
+
getCNPGScheduledBackupLastSchedule,
|
|
8
|
+
getCNPGScheduledBackupNextSchedule,
|
|
9
|
+
getCNPGScheduledBackupIsSuspended,
|
|
10
|
+
getCNPGScheduledBackupIsImmediate,
|
|
11
|
+
getCNPGScheduledBackupOwnerRef,
|
|
12
|
+
} from '../resource-utils-cnpg'
|
|
13
|
+
|
|
14
|
+
interface CNPGScheduledBackupRendererProps {
|
|
15
|
+
data: any
|
|
16
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function CNPGScheduledBackupRenderer({ data, onNavigate }: CNPGScheduledBackupRendererProps) {
|
|
20
|
+
const isSuspended = getCNPGScheduledBackupIsSuspended(data)
|
|
21
|
+
const clusterName = getCNPGScheduledBackupCluster(data)
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
{/* Suspended alert */}
|
|
26
|
+
{isSuspended && (
|
|
27
|
+
<AlertBanner
|
|
28
|
+
variant="warning"
|
|
29
|
+
title="Schedule Suspended"
|
|
30
|
+
message="This scheduled backup is currently suspended. No new backups will be created."
|
|
31
|
+
/>
|
|
32
|
+
)}
|
|
33
|
+
|
|
34
|
+
{/* Schedule */}
|
|
35
|
+
<Section title="Schedule" icon={Clock} defaultExpanded>
|
|
36
|
+
<PropertyList>
|
|
37
|
+
<Property label="Cron Expression" value={getCNPGScheduleCron(data)} />
|
|
38
|
+
<Property label="Last Schedule" value={getCNPGScheduledBackupLastSchedule(data)} />
|
|
39
|
+
<Property label="Next Schedule" value={getCNPGScheduledBackupNextSchedule(data)} />
|
|
40
|
+
<Property label="Suspended" value={isSuspended ? 'Yes' : 'No'} />
|
|
41
|
+
<Property label="Immediate" value={getCNPGScheduledBackupIsImmediate(data) ? 'Yes' : 'No'} />
|
|
42
|
+
</PropertyList>
|
|
43
|
+
</Section>
|
|
44
|
+
|
|
45
|
+
{/* Backup Configuration */}
|
|
46
|
+
<Section title="Backup Configuration" icon={Database} defaultExpanded>
|
|
47
|
+
<PropertyList>
|
|
48
|
+
<Property label="Cluster" value={(() => {
|
|
49
|
+
if (clusterName && clusterName !== '-') {
|
|
50
|
+
return (
|
|
51
|
+
<ResourceLink
|
|
52
|
+
name={clusterName}
|
|
53
|
+
kind="clusters"
|
|
54
|
+
namespace={data.metadata?.namespace || ''}
|
|
55
|
+
group="postgresql.cnpg.io"
|
|
56
|
+
onNavigate={onNavigate}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
return clusterName
|
|
61
|
+
})()} />
|
|
62
|
+
<Property label="Method" value={getCNPGScheduledBackupMethod(data)} />
|
|
63
|
+
<Property label="Owner Reference" value={getCNPGScheduledBackupOwnerRef(data)} />
|
|
64
|
+
</PropertyList>
|
|
65
|
+
</Section>
|
|
66
|
+
</>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { Shield, Clock, Globe } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
|
|
5
|
+
interface CertificateRendererProps {
|
|
6
|
+
data: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatDate(dateStr: string): string {
|
|
10
|
+
if (!dateStr) return '-'
|
|
11
|
+
const d = new Date(dateStr)
|
|
12
|
+
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getDaysUntil(dateStr: string): number {
|
|
16
|
+
const target = new Date(dateStr).getTime()
|
|
17
|
+
const now = Date.now()
|
|
18
|
+
return Math.floor((target - now) / (1000 * 60 * 60 * 24))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function CertificateRenderer({ data }: CertificateRendererProps) {
|
|
22
|
+
const spec = data.spec || {}
|
|
23
|
+
const status = data.status || {}
|
|
24
|
+
const conditions = status.conditions || []
|
|
25
|
+
const dnsNames = spec.dnsNames || []
|
|
26
|
+
const issuerRef = spec.issuerRef || {}
|
|
27
|
+
const usages = spec.usages || []
|
|
28
|
+
|
|
29
|
+
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
30
|
+
const isReady = readyCond?.status === 'True'
|
|
31
|
+
const isNotReady = readyCond?.status === 'False'
|
|
32
|
+
|
|
33
|
+
const notAfter = status.notAfter
|
|
34
|
+
const notBefore = status.notBefore
|
|
35
|
+
const renewalTime = status.renewalTime
|
|
36
|
+
|
|
37
|
+
const daysUntilExpiry = notAfter ? getDaysUntil(notAfter) : null
|
|
38
|
+
const isExpired = daysUntilExpiry !== null && daysUntilExpiry < 0
|
|
39
|
+
const expiresWithin7Days = daysUntilExpiry !== null && !isExpired && daysUntilExpiry <= 7
|
|
40
|
+
const expiresWithin30Days = daysUntilExpiry !== null && !isExpired && !expiresWithin7Days && daysUntilExpiry <= 30
|
|
41
|
+
|
|
42
|
+
// Progress bar calculation
|
|
43
|
+
let progressPct = 0
|
|
44
|
+
let progressColor = 'bg-green-500'
|
|
45
|
+
if (notBefore && notAfter) {
|
|
46
|
+
const now = Date.now()
|
|
47
|
+
const start = new Date(notBefore).getTime()
|
|
48
|
+
const end = new Date(notAfter).getTime()
|
|
49
|
+
progressPct = Math.min(100, Math.max(0, ((now - start) / (end - start)) * 100))
|
|
50
|
+
if (progressPct >= 90) {
|
|
51
|
+
progressColor = 'bg-red-500'
|
|
52
|
+
} else if (progressPct >= 70) {
|
|
53
|
+
progressColor = 'bg-yellow-500'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Expiry remaining text color
|
|
58
|
+
const expiryTextColor = isExpired || expiresWithin7Days
|
|
59
|
+
? 'text-red-400'
|
|
60
|
+
: expiresWithin30Days
|
|
61
|
+
? 'text-yellow-400'
|
|
62
|
+
: 'text-green-400'
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
{/* Problem detection alerts */}
|
|
67
|
+
{isNotReady && (
|
|
68
|
+
<AlertBanner
|
|
69
|
+
variant="error"
|
|
70
|
+
title="Certificate Not Ready"
|
|
71
|
+
message={<>{readyCond.reason && <span className="font-medium">{readyCond.reason}: </span>}{readyCond.message || 'The certificate is not in a ready state.'}</>}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{isExpired && (
|
|
76
|
+
<AlertBanner
|
|
77
|
+
variant="error"
|
|
78
|
+
title="Certificate has expired"
|
|
79
|
+
message={`Expired ${formatDate(notAfter)}. Renewal may be pending or failing.`}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
{expiresWithin7Days && (
|
|
84
|
+
<AlertBanner
|
|
85
|
+
variant="error"
|
|
86
|
+
title={`Certificate expires in ${daysUntilExpiry} day${daysUntilExpiry !== 1 ? 's' : ''}`}
|
|
87
|
+
message="Check that cert-manager is renewing this certificate."
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
{expiresWithin30Days && (
|
|
92
|
+
<AlertBanner
|
|
93
|
+
variant="warning"
|
|
94
|
+
title={`Certificate expires in ${daysUntilExpiry} day${daysUntilExpiry !== 1 ? 's' : ''}`}
|
|
95
|
+
message="Renewal should happen automatically before expiry."
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{/* Certificate Info */}
|
|
100
|
+
<Section title="Certificate Info" icon={Shield}>
|
|
101
|
+
<PropertyList>
|
|
102
|
+
<Property
|
|
103
|
+
label="Status"
|
|
104
|
+
value={
|
|
105
|
+
<span className={clsx(
|
|
106
|
+
'px-2 py-0.5 rounded text-xs font-medium',
|
|
107
|
+
isReady
|
|
108
|
+
? 'bg-green-500/20 text-green-400'
|
|
109
|
+
: 'bg-red-500/20 text-red-400'
|
|
110
|
+
)}>
|
|
111
|
+
{isReady ? 'Ready' : 'Not Ready'}
|
|
112
|
+
</span>
|
|
113
|
+
}
|
|
114
|
+
/>
|
|
115
|
+
<Property label="Secret Name" value={spec.secretName} />
|
|
116
|
+
<Property label="Revision" value={status.revision} />
|
|
117
|
+
</PropertyList>
|
|
118
|
+
</Section>
|
|
119
|
+
|
|
120
|
+
{/* Validity */}
|
|
121
|
+
<Section title="Validity" icon={Clock}>
|
|
122
|
+
<PropertyList>
|
|
123
|
+
<Property label="Not Before" value={notBefore ? formatDate(notBefore) : '-'} />
|
|
124
|
+
<Property
|
|
125
|
+
label="Not After"
|
|
126
|
+
value={
|
|
127
|
+
notAfter ? (
|
|
128
|
+
<span>
|
|
129
|
+
{formatDate(notAfter)}
|
|
130
|
+
{daysUntilExpiry !== null && (
|
|
131
|
+
<span className={clsx('ml-2 text-xs', expiryTextColor)}>
|
|
132
|
+
{isExpired
|
|
133
|
+
? `(expired ${-daysUntilExpiry}d ago)`
|
|
134
|
+
: `(${daysUntilExpiry}d remaining)`}
|
|
135
|
+
</span>
|
|
136
|
+
)}
|
|
137
|
+
</span>
|
|
138
|
+
) : '-'
|
|
139
|
+
}
|
|
140
|
+
/>
|
|
141
|
+
<Property label="Renewal Time" value={renewalTime ? formatDate(renewalTime) : '-'} />
|
|
142
|
+
</PropertyList>
|
|
143
|
+
|
|
144
|
+
{/* Progress bar */}
|
|
145
|
+
{notBefore && notAfter && (
|
|
146
|
+
<div className="mt-3">
|
|
147
|
+
<div className="flex items-center justify-between text-xs text-theme-text-tertiary mb-1">
|
|
148
|
+
<span>{formatDate(notBefore)}</span>
|
|
149
|
+
<span>{formatDate(notAfter)}</span>
|
|
150
|
+
</div>
|
|
151
|
+
<div className="h-2 bg-theme-hover rounded overflow-hidden">
|
|
152
|
+
<div
|
|
153
|
+
className={clsx('h-full transition-all', progressColor)}
|
|
154
|
+
style={{ width: `${progressPct}%` }}
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
<div className="text-xs text-theme-text-tertiary mt-1 text-center">
|
|
158
|
+
{Math.round(progressPct)}% elapsed
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
</Section>
|
|
163
|
+
|
|
164
|
+
{/* Domains */}
|
|
165
|
+
{dnsNames.length > 0 && (
|
|
166
|
+
<Section title="Domains" icon={Globe}>
|
|
167
|
+
<div className="flex flex-wrap gap-1">
|
|
168
|
+
{dnsNames.map((name: string) => (
|
|
169
|
+
<span
|
|
170
|
+
key={name}
|
|
171
|
+
className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
|
|
172
|
+
>
|
|
173
|
+
{name}
|
|
174
|
+
</span>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
</Section>
|
|
178
|
+
)}
|
|
179
|
+
|
|
180
|
+
{/* Issuer */}
|
|
181
|
+
<Section title="Issuer">
|
|
182
|
+
<PropertyList>
|
|
183
|
+
<Property label="Kind" value={issuerRef.kind} />
|
|
184
|
+
<Property label="Name" value={issuerRef.name} />
|
|
185
|
+
<Property label="Group" value={issuerRef.group} />
|
|
186
|
+
</PropertyList>
|
|
187
|
+
</Section>
|
|
188
|
+
|
|
189
|
+
{/* Usages */}
|
|
190
|
+
{usages.length > 0 && (
|
|
191
|
+
<Section title="Usages">
|
|
192
|
+
<div className="flex flex-wrap gap-1">
|
|
193
|
+
{usages.map((usage: string) => (
|
|
194
|
+
<span
|
|
195
|
+
key={usage}
|
|
196
|
+
className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
|
|
197
|
+
>
|
|
198
|
+
{usage}
|
|
199
|
+
</span>
|
|
200
|
+
))}
|
|
201
|
+
</div>
|
|
202
|
+
</Section>
|
|
203
|
+
)}
|
|
204
|
+
|
|
205
|
+
{/* Conditions */}
|
|
206
|
+
<ConditionsSection conditions={conditions} />
|
|
207
|
+
</>
|
|
208
|
+
)
|
|
209
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Shield, FileText, Info } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
+
|
|
5
|
+
interface CertificateRequestRendererProps {
|
|
6
|
+
data: any
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function CertificateRequestRenderer({ data }: CertificateRequestRendererProps) {
|
|
10
|
+
const spec = data.spec || {}
|
|
11
|
+
const status = data.status || {}
|
|
12
|
+
const conditions = status.conditions || []
|
|
13
|
+
const issuerRef = spec.issuerRef || {}
|
|
14
|
+
const usages = spec.usages || []
|
|
15
|
+
const ownerRefs = data.metadata?.ownerReferences || []
|
|
16
|
+
|
|
17
|
+
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
18
|
+
const approvedCond = conditions.find((c: any) => c.type === 'Approved')
|
|
19
|
+
const deniedCond = conditions.find((c: any) => c.type === 'Denied')
|
|
20
|
+
|
|
21
|
+
const isReady = readyCond?.status === 'True'
|
|
22
|
+
const isNotReady = readyCond?.status === 'False'
|
|
23
|
+
const isApproved = approvedCond?.status === 'True'
|
|
24
|
+
const isDenied = deniedCond?.status === 'True'
|
|
25
|
+
|
|
26
|
+
const ownerCertificate = ownerRefs.find((ref: any) => ref.kind === 'Certificate')
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<>
|
|
30
|
+
{/* Problem detection alerts */}
|
|
31
|
+
{isNotReady && (
|
|
32
|
+
<AlertBanner
|
|
33
|
+
variant="error"
|
|
34
|
+
title="Certificate Request Not Ready"
|
|
35
|
+
message={<>{readyCond.reason && <span className="font-medium">{readyCond.reason}: </span>}{readyCond.message || 'The certificate request is not in a ready state.'}</>}
|
|
36
|
+
/>
|
|
37
|
+
)}
|
|
38
|
+
|
|
39
|
+
{isDenied && (
|
|
40
|
+
<AlertBanner
|
|
41
|
+
variant="error"
|
|
42
|
+
title="Certificate request was denied"
|
|
43
|
+
message={<>{deniedCond.reason && <span className="font-medium">{deniedCond.reason}: </span>}{deniedCond.message || 'The certificate request has been denied by the approver.'}</>}
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
|
|
47
|
+
{/* Status */}
|
|
48
|
+
<Section title="Status" icon={Shield}>
|
|
49
|
+
<PropertyList>
|
|
50
|
+
<Property
|
|
51
|
+
label="Ready"
|
|
52
|
+
value={
|
|
53
|
+
<span className={clsx(
|
|
54
|
+
'px-2 py-0.5 rounded text-xs font-medium',
|
|
55
|
+
isReady
|
|
56
|
+
? 'bg-green-500/20 text-green-400'
|
|
57
|
+
: 'bg-red-500/20 text-red-400'
|
|
58
|
+
)}>
|
|
59
|
+
{isReady ? 'Ready' : 'Not Ready'}
|
|
60
|
+
</span>
|
|
61
|
+
}
|
|
62
|
+
/>
|
|
63
|
+
<Property
|
|
64
|
+
label="Approved"
|
|
65
|
+
value={
|
|
66
|
+
<span className={clsx(
|
|
67
|
+
'px-2 py-0.5 rounded text-xs font-medium',
|
|
68
|
+
isApproved
|
|
69
|
+
? 'bg-green-500/20 text-green-400'
|
|
70
|
+
: isDenied
|
|
71
|
+
? 'bg-red-500/20 text-red-400'
|
|
72
|
+
: 'bg-yellow-500/20 text-yellow-400'
|
|
73
|
+
)}>
|
|
74
|
+
{isApproved ? 'Yes' : isDenied ? 'No' : 'Pending'}
|
|
75
|
+
</span>
|
|
76
|
+
}
|
|
77
|
+
/>
|
|
78
|
+
<Property label="Reason" value={readyCond?.reason} />
|
|
79
|
+
{ownerCertificate && (
|
|
80
|
+
<Property label="Owner Certificate" value={ownerCertificate.name} />
|
|
81
|
+
)}
|
|
82
|
+
</PropertyList>
|
|
83
|
+
</Section>
|
|
84
|
+
|
|
85
|
+
{/* Issuer */}
|
|
86
|
+
<Section title="Issuer" icon={Info}>
|
|
87
|
+
<PropertyList>
|
|
88
|
+
<Property label="Kind" value={issuerRef.kind} />
|
|
89
|
+
<Property label="Name" value={issuerRef.name} />
|
|
90
|
+
<Property label="Group" value={issuerRef.group} />
|
|
91
|
+
</PropertyList>
|
|
92
|
+
</Section>
|
|
93
|
+
|
|
94
|
+
{/* Request Details */}
|
|
95
|
+
<Section title="Request Details" icon={FileText}>
|
|
96
|
+
<PropertyList>
|
|
97
|
+
<Property label="Duration" value={spec.duration} />
|
|
98
|
+
{usages.length > 0 && (
|
|
99
|
+
<Property
|
|
100
|
+
label="Usages"
|
|
101
|
+
value={
|
|
102
|
+
<div className="flex flex-wrap gap-1">
|
|
103
|
+
{usages.map((usage: string) => (
|
|
104
|
+
<span
|
|
105
|
+
key={usage}
|
|
106
|
+
className="px-2 py-0.5 bg-theme-elevated rounded text-xs text-theme-text-secondary"
|
|
107
|
+
>
|
|
108
|
+
{usage}
|
|
109
|
+
</span>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
<Property label="Has Certificate" value={status.certificate ? 'Yes' : 'No'} />
|
|
116
|
+
</PropertyList>
|
|
117
|
+
</Section>
|
|
118
|
+
|
|
119
|
+
{/* Conditions */}
|
|
120
|
+
<ConditionsSection conditions={conditions} />
|
|
121
|
+
</>
|
|
122
|
+
)
|
|
123
|
+
}
|