@skyhook-io/k8s-ui 1.3.2 → 1.4.1
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 +2 -2
- package/src/components/audit/AuditAlerts.tsx +88 -0
- package/src/components/audit/AuditCard.tsx +130 -0
- package/src/components/audit/AuditFindingsTable.tsx +526 -0
- package/src/components/audit/index.ts +3 -0
- package/src/components/dock/DockContext.tsx +5 -2
- package/src/components/dock/LocalTerminalTab.tsx +11 -0
- package/src/components/resources/ResourcesView.tsx +126 -49
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +190 -28
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +22 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +156 -5
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +28 -1
- package/src/components/resources/renderers/CertificateRenderer.tsx +47 -8
- package/src/components/resources/renderers/ChallengeRenderer.tsx +22 -3
- package/src/components/resources/renderers/CiliumNetworkPolicyRenderer.tsx +445 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +81 -9
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +10 -3
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +59 -1
- package/src/components/resources/renderers/ClusterNetworkPolicyRenderer.tsx +160 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +3 -5
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +91 -3
- package/src/components/resources/renderers/GenericRenderer.tsx +1 -1
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +3 -42
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -41
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +66 -2
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +31 -1
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +66 -1
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +3 -0
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +55 -1
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +23 -0
- package/src/components/resources/renderers/KustomizationRenderer.tsx +38 -3
- package/src/components/resources/renderers/NetworkPolicyDiagram.tsx +277 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +17 -57
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +3 -42
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +3 -8
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +5 -7
- package/src/components/resources/renderers/PodRenderer.tsx +22 -74
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +178 -20
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +2 -2
- package/src/components/resources/renderers/RolloutRenderer.tsx +105 -3
- package/src/components/resources/renderers/SbomReportRenderer.tsx +46 -5
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +11 -2
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +5 -7
- package/src/components/resources/renderers/ServiceRenderer.tsx +40 -28
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +11 -3
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +11 -3
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +8 -16
- package/src/components/resources/renderers/WorkflowRenderer.tsx +71 -17
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +46 -1
- package/src/components/resources/renderers/index.ts +3 -0
- package/src/components/resources/renderers/karpenter-cells.tsx +18 -3
- package/src/components/resources/resource-utils-cnpg.ts +47 -0
- package/src/components/resources/resource-utils-karpenter.ts +6 -0
- package/src/components/resources/resource-utils-prometheus.ts +50 -6
- package/src/components/resources/resource-utils.ts +131 -33
- package/src/components/shared/CreateResourceDialog.tsx +236 -0
- package/src/components/shared/EditableYamlView.tsx +16 -1
- package/src/components/shared/ResourceActionsBar.tsx +9 -7
- package/src/components/shared/ResourceRendererDispatch.tsx +13 -6
- package/src/components/shared/index.ts +1 -0
- package/src/components/timeline/TimelineList.tsx +11 -4
- package/src/components/topology/K8sResourceNode.tsx +15 -0
- package/src/components/topology/TopologyControls.tsx +21 -1
- package/src/components/topology/TopologyGraph.tsx +1 -1
- package/src/components/ui/Badge.tsx +8 -0
- package/src/components/ui/drawer-components.tsx +68 -5
- package/src/components/workload/WorkloadView.tsx +55 -29
- package/src/index.ts +3 -0
- package/src/theme/variables.css +1 -1
- package/src/types/core.ts +2 -1
- package/src/types/gitops.ts +8 -0
- package/src/utils/badge-colors.ts +7 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/k8s-errors.ts +142 -0
- package/src/utils/navigation.test.ts +29 -2
- package/src/utils/navigation.ts +45 -24
- package/src/utils/resource-icons.ts +3 -0
- package/src/utils/skeleton-yaml.ts +302 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { Database, HardDrive, Activity, Clock, Shield } from 'lucide-react'
|
|
1
|
+
import { Database, HardDrive, Activity, Clock, Shield, KeyRound } from 'lucide-react'
|
|
2
2
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
3
3
|
import {
|
|
4
4
|
getCNPGClusterInstances,
|
|
5
|
-
getCNPGClusterPrimary,
|
|
6
5
|
getCNPGClusterPhase,
|
|
7
6
|
getCNPGClusterImage,
|
|
8
7
|
getCNPGClusterStorage,
|
|
@@ -16,6 +15,8 @@ import {
|
|
|
16
15
|
getCNPGClusterReplicaSource,
|
|
17
16
|
getCNPGClusterInstanceNames,
|
|
18
17
|
getCNPGClusterPostgresParams,
|
|
18
|
+
getCNPGClusterInstancesReportedState,
|
|
19
|
+
getCNPGClusterCertificateExpirations,
|
|
19
20
|
} from '../resource-utils-cnpg'
|
|
20
21
|
|
|
21
22
|
interface CNPGClusterRendererProps {
|
|
@@ -35,15 +36,49 @@ export function CNPGClusterRenderer({ data, onNavigate }: CNPGClusterRendererPro
|
|
|
35
36
|
const postgresParams = getCNPGClusterPostgresParams(data)
|
|
36
37
|
const instanceNames = getCNPGClusterInstanceNames(data)
|
|
37
38
|
const bootstrapMethod = getCNPGClusterBootstrapMethod(data)
|
|
39
|
+
const reportedState = getCNPGClusterInstancesReportedState(data)
|
|
40
|
+
const certExpirations = getCNPGClusterCertificateExpirations(data)
|
|
41
|
+
|
|
42
|
+
// Primary mismatch detection
|
|
43
|
+
const targetPrimary = data.status?.targetPrimary
|
|
44
|
+
const currentPrimary = data.status?.currentPrimary
|
|
45
|
+
const primaryMismatch = targetPrimary && currentPrimary && targetPrimary !== currentPrimary
|
|
46
|
+
|
|
47
|
+
// Split-brain detection: multiple instances report isPrimary
|
|
48
|
+
const primariesReported = reportedState.filter(i => i.isPrimary)
|
|
49
|
+
const hasSplitBrain = primariesReported.length > 1
|
|
50
|
+
|
|
51
|
+
// Certificate expiry warnings
|
|
52
|
+
const expiredCerts = certExpirations.filter(c => c.daysUntilExpiry < 0)
|
|
53
|
+
const criticalCerts = certExpirations.filter(c => c.daysUntilExpiry >= 0 && c.daysUntilExpiry <= 7)
|
|
54
|
+
const warningCerts = certExpirations.filter(c => c.daysUntilExpiry > 7 && c.daysUntilExpiry <= 30)
|
|
55
|
+
|
|
56
|
+
// Last failed backup
|
|
57
|
+
const lastFailedBackup = data.status?.lastFailedBackup
|
|
38
58
|
|
|
39
59
|
// Problem detection
|
|
40
|
-
const
|
|
60
|
+
const isDown = instances > 0 && readyInstances === 0
|
|
61
|
+
const isDegraded = instances > 0 && readyInstances < instances && readyInstances > 0
|
|
41
62
|
const isFailover = phase.toLowerCase().includes('failing over')
|
|
42
63
|
const isSwitchover = phase.toLowerCase().includes('switchover')
|
|
43
64
|
|
|
44
65
|
return (
|
|
45
66
|
<>
|
|
46
67
|
{/* Problem alerts */}
|
|
68
|
+
{hasSplitBrain && (
|
|
69
|
+
<AlertBanner
|
|
70
|
+
variant="error"
|
|
71
|
+
title="Potential Split-Brain Detected"
|
|
72
|
+
message={`Multiple instances report as primary: ${primariesReported.map(i => i.podName).join(', ')}. Immediate investigation required.`}
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
{isDown && (
|
|
76
|
+
<AlertBanner
|
|
77
|
+
variant="error"
|
|
78
|
+
title="Cluster Down"
|
|
79
|
+
message={`All ${instances} instances are not ready.`}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
47
82
|
{isDegraded && (
|
|
48
83
|
<AlertBanner
|
|
49
84
|
variant="warning"
|
|
@@ -65,15 +100,76 @@ export function CNPGClusterRenderer({ data, onNavigate }: CNPGClusterRendererPro
|
|
|
65
100
|
message={`Cluster is performing a switchover. Current phase: ${phase}`}
|
|
66
101
|
/>
|
|
67
102
|
)}
|
|
103
|
+
{primaryMismatch && (
|
|
104
|
+
<AlertBanner
|
|
105
|
+
variant="warning"
|
|
106
|
+
title="Switchover Pending"
|
|
107
|
+
message={`Target primary is ${targetPrimary} but current primary is ${currentPrimary}.`}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
{lastFailedBackup && (
|
|
111
|
+
<AlertBanner
|
|
112
|
+
variant="error"
|
|
113
|
+
title="Last Backup Failed"
|
|
114
|
+
message={`Last backup failed at ${lastFailedBackup}. WAL archiving may be impacted and RPO is growing.`}
|
|
115
|
+
/>
|
|
116
|
+
)}
|
|
117
|
+
{expiredCerts.length > 0 && (
|
|
118
|
+
<AlertBanner
|
|
119
|
+
variant="error"
|
|
120
|
+
title="Certificate Expired"
|
|
121
|
+
items={expiredCerts.map(c => `${c.secretName} expired ${Math.abs(c.daysUntilExpiry)} day${Math.abs(c.daysUntilExpiry) === 1 ? '' : 's'} ago (${c.expiryDate})`)}
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
124
|
+
{criticalCerts.length > 0 && (
|
|
125
|
+
<AlertBanner
|
|
126
|
+
variant="error"
|
|
127
|
+
title="Certificate Expiring Soon"
|
|
128
|
+
items={criticalCerts.map(c => `${c.secretName} expires in ${c.daysUntilExpiry} day${c.daysUntilExpiry === 1 ? '' : 's'} (${c.expiryDate})`)}
|
|
129
|
+
/>
|
|
130
|
+
)}
|
|
131
|
+
{warningCerts.length > 0 && (
|
|
132
|
+
<AlertBanner
|
|
133
|
+
variant="warning"
|
|
134
|
+
title="Certificate Expiry Warning"
|
|
135
|
+
items={warningCerts.map(c => `${c.secretName} expires in ${c.daysUntilExpiry} days (${c.expiryDate})`)}
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
68
138
|
|
|
69
139
|
{/* Cluster Overview */}
|
|
70
140
|
<Section title="Cluster Overview" icon={Database} defaultExpanded>
|
|
71
141
|
<PropertyList>
|
|
72
142
|
<Property label="Phase" value={phase} />
|
|
73
143
|
<Property label="Instances" value={getCNPGClusterInstances(data)} />
|
|
74
|
-
<Property label="Current Primary" value={
|
|
144
|
+
<Property label="Current Primary" value={currentPrimary || '-'} />
|
|
145
|
+
{targetPrimary && targetPrimary !== currentPrimary && (
|
|
146
|
+
<Property label="Target Primary" value={targetPrimary} />
|
|
147
|
+
)}
|
|
75
148
|
<Property label="Image" value={getCNPGClusterImage(data)} />
|
|
76
149
|
<Property label="Update Strategy" value={getCNPGClusterUpdateStrategy(data)} />
|
|
150
|
+
{data.status?.writeService && (
|
|
151
|
+
<Property label="Write Service" value={
|
|
152
|
+
<ResourceLink name={data.status.writeService} kind="Service" namespace={data.metadata?.namespace || ''} onNavigate={onNavigate} />
|
|
153
|
+
} />
|
|
154
|
+
)}
|
|
155
|
+
{data.status?.readService && (
|
|
156
|
+
<Property label="Read Service" value={
|
|
157
|
+
<ResourceLink name={data.status.readService} kind="Service" namespace={data.metadata?.namespace || ''} onNavigate={onNavigate} />
|
|
158
|
+
} />
|
|
159
|
+
)}
|
|
160
|
+
{data.spec?.enableSuperuserAccess !== undefined && (
|
|
161
|
+
<Property label="Superuser Access" value={
|
|
162
|
+
<span className={`badge-sm ${data.spec.enableSuperuserAccess ? 'bg-green-500/20 text-green-400' : 'bg-theme-hover text-theme-text-secondary'}`}>
|
|
163
|
+
{data.spec.enableSuperuserAccess ? 'Enabled' : 'Disabled'}
|
|
164
|
+
</span>
|
|
165
|
+
} />
|
|
166
|
+
)}
|
|
167
|
+
{(data.spec?.minSyncReplicas !== undefined || data.spec?.maxSyncReplicas !== undefined) && (
|
|
168
|
+
<Property
|
|
169
|
+
label="Sync Replicas"
|
|
170
|
+
value={`Min: ${data.spec?.minSyncReplicas ?? '-'} / Max: ${data.spec?.maxSyncReplicas ?? '-'}`}
|
|
171
|
+
/>
|
|
172
|
+
)}
|
|
77
173
|
</PropertyList>
|
|
78
174
|
{instanceNames.length > 0 && (
|
|
79
175
|
<div className="mt-2 pt-2 border-t border-theme-border">
|
|
@@ -92,6 +188,29 @@ export function CNPGClusterRenderer({ data, onNavigate }: CNPGClusterRendererPro
|
|
|
92
188
|
)}
|
|
93
189
|
</Section>
|
|
94
190
|
|
|
191
|
+
{/* Replication State */}
|
|
192
|
+
{reportedState.length > 0 && (
|
|
193
|
+
<Section title="Replication" icon={Activity} defaultExpanded>
|
|
194
|
+
<div className="space-y-1.5">
|
|
195
|
+
{reportedState.map((instance) => (
|
|
196
|
+
<div key={instance.podName} className="flex items-center gap-2 text-sm">
|
|
197
|
+
<span className="text-theme-text-primary font-mono flex-1 break-all">{instance.podName}</span>
|
|
198
|
+
<span className={
|
|
199
|
+
instance.isPrimary
|
|
200
|
+
? 'badge-sm bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300'
|
|
201
|
+
: 'badge-sm bg-theme-elevated text-theme-text-secondary'
|
|
202
|
+
}>
|
|
203
|
+
{instance.isPrimary ? 'Primary' : 'Replica'}
|
|
204
|
+
</span>
|
|
205
|
+
{instance.timelineID != null && (
|
|
206
|
+
<span className="text-xs text-theme-text-tertiary">TL {instance.timelineID}</span>
|
|
207
|
+
)}
|
|
208
|
+
</div>
|
|
209
|
+
))}
|
|
210
|
+
</div>
|
|
211
|
+
</Section>
|
|
212
|
+
)}
|
|
213
|
+
|
|
95
214
|
{/* Storage */}
|
|
96
215
|
<Section title="Storage" icon={HardDrive} defaultExpanded>
|
|
97
216
|
<PropertyList>
|
|
@@ -131,6 +250,9 @@ export function CNPGClusterRenderer({ data, onNavigate }: CNPGClusterRendererPro
|
|
|
131
250
|
{backupConfig.lastSuccessfulBackup && (
|
|
132
251
|
<Property label="Last Successful" value={backupConfig.lastSuccessfulBackup} />
|
|
133
252
|
)}
|
|
253
|
+
{lastFailedBackup && (
|
|
254
|
+
<Property label="Last Failed" value={lastFailedBackup} />
|
|
255
|
+
)}
|
|
134
256
|
{backupConfig.firstRecoverabilityPoint && (
|
|
135
257
|
<Property label="First Recoverability" value={backupConfig.firstRecoverabilityPoint} />
|
|
136
258
|
)}
|
|
@@ -138,6 +260,35 @@ export function CNPGClusterRenderer({ data, onNavigate }: CNPGClusterRendererPro
|
|
|
138
260
|
</Section>
|
|
139
261
|
)}
|
|
140
262
|
|
|
263
|
+
{/* Certificates */}
|
|
264
|
+
{certExpirations.length > 0 && (
|
|
265
|
+
<Section title="Certificates" icon={KeyRound} defaultExpanded>
|
|
266
|
+
<div className="space-y-1.5">
|
|
267
|
+
{certExpirations.map((cert) => (
|
|
268
|
+
<div key={cert.secretName} className="flex items-center gap-2 text-sm">
|
|
269
|
+
<span className="text-theme-text-primary font-mono flex-1 break-all">{cert.secretName}</span>
|
|
270
|
+
<span className="text-xs text-theme-text-tertiary">{cert.expiryDate}</span>
|
|
271
|
+
{cert.daysUntilExpiry <= 7 && (
|
|
272
|
+
<span className="badge-sm bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300">
|
|
273
|
+
{cert.daysUntilExpiry}d
|
|
274
|
+
</span>
|
|
275
|
+
)}
|
|
276
|
+
{cert.daysUntilExpiry > 7 && cert.daysUntilExpiry <= 30 && (
|
|
277
|
+
<span className="badge-sm bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300">
|
|
278
|
+
{cert.daysUntilExpiry}d
|
|
279
|
+
</span>
|
|
280
|
+
)}
|
|
281
|
+
{cert.daysUntilExpiry > 30 && (
|
|
282
|
+
<span className="badge-sm bg-theme-elevated text-theme-text-secondary">
|
|
283
|
+
{cert.daysUntilExpiry}d
|
|
284
|
+
</span>
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
))}
|
|
288
|
+
</div>
|
|
289
|
+
</Section>
|
|
290
|
+
)}
|
|
291
|
+
|
|
141
292
|
{/* Monitoring */}
|
|
142
293
|
{(monitoring.podMonitorEnabled || (monitoring.customQueriesConfigMap && monitoring.customQueriesConfigMap.length > 0)) && (
|
|
143
294
|
<Section title="Monitoring" icon={Activity} defaultExpanded>
|
|
@@ -165,7 +316,7 @@ export function CNPGClusterRenderer({ data, onNavigate }: CNPGClusterRendererPro
|
|
|
165
316
|
|
|
166
317
|
{/* Replication - only if this is a replica cluster */}
|
|
167
318
|
{isReplica && (
|
|
168
|
-
<Section title="
|
|
319
|
+
<Section title="Replica Cluster" icon={Shield} defaultExpanded>
|
|
169
320
|
<PropertyList>
|
|
170
321
|
<Property label="Role" value="Replica" />
|
|
171
322
|
<Property label="Source" value={getCNPGClusterReplicaSource(data)} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Users, Database } from 'lucide-react'
|
|
1
|
+
import { Users, Database, KeyRound } from 'lucide-react'
|
|
2
2
|
import { Section, PropertyList, Property, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
3
3
|
import {
|
|
4
4
|
getCNPGPoolerCluster,
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
getCNPGPoolerMode,
|
|
7
7
|
getCNPGPoolerInstances,
|
|
8
8
|
getCNPGPoolerParameters,
|
|
9
|
+
getCNPGPoolerAuthQuery,
|
|
10
|
+
getCNPGPoolerAuthQuerySecret,
|
|
9
11
|
} from '../resource-utils-cnpg'
|
|
10
12
|
|
|
11
13
|
interface CNPGPoolerRendererProps {
|
|
@@ -19,6 +21,8 @@ export function CNPGPoolerRenderer({ data, onNavigate }: CNPGPoolerRendererProps
|
|
|
19
21
|
const isDegraded = desired > 0 && ready < desired
|
|
20
22
|
const clusterName = getCNPGPoolerCluster(data)
|
|
21
23
|
const parameters = getCNPGPoolerParameters(data)
|
|
24
|
+
const authQuery = getCNPGPoolerAuthQuery(data)
|
|
25
|
+
const authQuerySecret = getCNPGPoolerAuthQuerySecret(data)
|
|
22
26
|
|
|
23
27
|
return (
|
|
24
28
|
<>
|
|
@@ -40,6 +44,29 @@ export function CNPGPoolerRenderer({ data, onNavigate }: CNPGPoolerRendererProps
|
|
|
40
44
|
</PropertyList>
|
|
41
45
|
</Section>
|
|
42
46
|
|
|
47
|
+
{/* Authentication */}
|
|
48
|
+
{(authQuery || authQuerySecret) && (
|
|
49
|
+
<Section title="Authentication" icon={KeyRound} defaultExpanded>
|
|
50
|
+
<PropertyList>
|
|
51
|
+
{authQuery && (
|
|
52
|
+
<Property label="Auth Query" value={
|
|
53
|
+
<code className="text-xs font-mono bg-theme-elevated px-1.5 py-0.5 rounded break-all">{authQuery}</code>
|
|
54
|
+
} />
|
|
55
|
+
)}
|
|
56
|
+
{authQuerySecret && (
|
|
57
|
+
<Property label="Auth Query Secret" value={
|
|
58
|
+
<ResourceLink
|
|
59
|
+
name={authQuerySecret.name}
|
|
60
|
+
kind="secrets"
|
|
61
|
+
namespace={data.metadata?.namespace || ''}
|
|
62
|
+
onNavigate={onNavigate}
|
|
63
|
+
/>
|
|
64
|
+
} />
|
|
65
|
+
)}
|
|
66
|
+
</PropertyList>
|
|
67
|
+
</Section>
|
|
68
|
+
)}
|
|
69
|
+
|
|
43
70
|
{/* Cluster Reference */}
|
|
44
71
|
<Section title="Cluster" icon={Database} defaultExpanded>
|
|
45
72
|
<PropertyList>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Shield, Clock, Globe } from 'lucide-react'
|
|
1
|
+
import { Shield, Clock, Globe, Key } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
4
4
|
|
|
@@ -26,6 +26,10 @@ export function CertificateRenderer({ data }: CertificateRendererProps) {
|
|
|
26
26
|
const issuerRef = spec.issuerRef || {}
|
|
27
27
|
const usages = spec.usages || []
|
|
28
28
|
|
|
29
|
+
const privateKey = spec.privateKey
|
|
30
|
+
const failedIssuanceAttempts = status.failedIssuanceAttempts
|
|
31
|
+
const lastFailureTime = status.lastFailureTime
|
|
32
|
+
|
|
29
33
|
const readyCond = conditions.find((c: any) => c.type === 'Ready')
|
|
30
34
|
const isReady = readyCond?.status === 'True'
|
|
31
35
|
const isNotReady = readyCond?.status === 'False'
|
|
@@ -96,30 +100,53 @@ export function CertificateRenderer({ data }: CertificateRendererProps) {
|
|
|
96
100
|
/>
|
|
97
101
|
)}
|
|
98
102
|
|
|
103
|
+
{failedIssuanceAttempts > 0 && (
|
|
104
|
+
<AlertBanner
|
|
105
|
+
variant="warning"
|
|
106
|
+
title={`${failedIssuanceAttempts} failed issuance attempt${failedIssuanceAttempts !== 1 ? 's' : ''}`}
|
|
107
|
+
message={lastFailureTime ? `Last failure: ${formatDate(lastFailureTime)}` : undefined}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
|
|
99
111
|
{/* Certificate Info */}
|
|
100
112
|
<Section title="Certificate Info" icon={Shield}>
|
|
101
113
|
<PropertyList>
|
|
102
114
|
<Property
|
|
103
115
|
label="Status"
|
|
104
116
|
value={
|
|
105
|
-
<span className=
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
<span className="flex items-center gap-2">
|
|
118
|
+
<span className={clsx(
|
|
119
|
+
'badge',
|
|
120
|
+
isReady
|
|
121
|
+
? 'bg-green-500/20 text-green-400'
|
|
122
|
+
: 'bg-red-500/20 text-red-400'
|
|
123
|
+
)}>
|
|
124
|
+
{isReady ? 'Ready' : 'Not Ready'}
|
|
125
|
+
</span>
|
|
126
|
+
{spec.isCA && (
|
|
127
|
+
<span className="badge bg-purple-500/20 text-purple-400">
|
|
128
|
+
CA Certificate
|
|
129
|
+
</span>
|
|
130
|
+
)}
|
|
112
131
|
</span>
|
|
113
132
|
}
|
|
114
133
|
/>
|
|
115
134
|
<Property label="Secret Name" value={spec.secretName} />
|
|
116
135
|
<Property label="Revision" value={status.revision} />
|
|
136
|
+
{failedIssuanceAttempts > 0 && (
|
|
137
|
+
<Property label="Failed Issuance Attempts" value={String(failedIssuanceAttempts)} />
|
|
138
|
+
)}
|
|
139
|
+
{lastFailureTime && (
|
|
140
|
+
<Property label="Last Failure Time" value={formatDate(lastFailureTime)} />
|
|
141
|
+
)}
|
|
117
142
|
</PropertyList>
|
|
118
143
|
</Section>
|
|
119
144
|
|
|
120
145
|
{/* Validity */}
|
|
121
146
|
<Section title="Validity" icon={Clock}>
|
|
122
147
|
<PropertyList>
|
|
148
|
+
<Property label="Duration" value={spec.duration} />
|
|
149
|
+
<Property label="Renew Before" value={spec.renewBefore} />
|
|
123
150
|
<Property label="Not Before" value={notBefore ? formatDate(notBefore) : '-'} />
|
|
124
151
|
<Property
|
|
125
152
|
label="Not After"
|
|
@@ -161,6 +188,18 @@ export function CertificateRenderer({ data }: CertificateRendererProps) {
|
|
|
161
188
|
)}
|
|
162
189
|
</Section>
|
|
163
190
|
|
|
191
|
+
{/* Private Key */}
|
|
192
|
+
{privateKey && (
|
|
193
|
+
<Section title="Private Key" icon={Key}>
|
|
194
|
+
<PropertyList>
|
|
195
|
+
<Property label="Algorithm" value={privateKey.algorithm} />
|
|
196
|
+
<Property label="Size" value={privateKey.size != null ? String(privateKey.size) : undefined} />
|
|
197
|
+
<Property label="Encoding" value={privateKey.encoding} />
|
|
198
|
+
<Property label="Rotation Policy" value={privateKey.rotationPolicy} />
|
|
199
|
+
</PropertyList>
|
|
200
|
+
</Section>
|
|
201
|
+
)}
|
|
202
|
+
|
|
164
203
|
{/* Domains */}
|
|
165
204
|
{dnsNames.length > 0 && (
|
|
166
205
|
<Section title="Domains" icon={Globe}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Shield, AlertTriangle, Globe, Key } from 'lucide-react'
|
|
1
|
+
import { Shield, AlertTriangle, Globe, Key, FileText } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { Section, PropertyList, Property, ConditionsSection } from '../../ui/drawer-components'
|
|
4
4
|
import { getSolverType } from './ClusterIssuerRenderer'
|
|
@@ -68,11 +68,19 @@ export function ChallengeRenderer({ data }: { data: any }) {
|
|
|
68
68
|
</span>
|
|
69
69
|
}
|
|
70
70
|
/>
|
|
71
|
+
{status.reason && !isError && <Property label="Reason" value={status.reason} />}
|
|
71
72
|
<Property
|
|
72
73
|
label="Type"
|
|
73
74
|
value={
|
|
74
|
-
<span className="
|
|
75
|
-
|
|
75
|
+
<span className="flex items-center gap-2">
|
|
76
|
+
<span className="badge bg-theme-elevated text-theme-text-secondary">
|
|
77
|
+
{challengeType}
|
|
78
|
+
</span>
|
|
79
|
+
{spec.wildcard && (
|
|
80
|
+
<span className="badge bg-purple-500/20 text-purple-400">
|
|
81
|
+
Wildcard
|
|
82
|
+
</span>
|
|
83
|
+
)}
|
|
76
84
|
</span>
|
|
77
85
|
}
|
|
78
86
|
/>
|
|
@@ -84,6 +92,17 @@ export function ChallengeRenderer({ data }: { data: any }) {
|
|
|
84
92
|
</PropertyList>
|
|
85
93
|
</Section>
|
|
86
94
|
|
|
95
|
+
{/* Issuer */}
|
|
96
|
+
{spec.issuerRef && (
|
|
97
|
+
<Section title="Issuer" icon={FileText}>
|
|
98
|
+
<PropertyList>
|
|
99
|
+
<Property label="Name" value={spec.issuerRef.name} />
|
|
100
|
+
<Property label="Kind" value={spec.issuerRef.kind} />
|
|
101
|
+
<Property label="Group" value={spec.issuerRef.group} />
|
|
102
|
+
</PropertyList>
|
|
103
|
+
</Section>
|
|
104
|
+
)}
|
|
105
|
+
|
|
87
106
|
{/* ACME */}
|
|
88
107
|
{(spec.url || spec.token) && (
|
|
89
108
|
<Section title="ACME" icon={Globe}>
|