@skyhook-io/k8s-ui 1.12.0 → 1.13.3
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 +4 -4
- package/src/components/applications/ApplicationDetail.tsx +2 -2
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +32 -7
- package/src/components/issues/severity.ts +1 -0
- package/src/components/resources/ResourcesView.empty-note.test.ts +41 -0
- package/src/components/resources/ResourcesView.tsx +419 -9
- package/src/components/resources/index.ts +27 -0
- package/src/components/resources/kyverno-legacy-action.test.ts +98 -0
- package/src/components/resources/renderers/AnalysisRunRenderer.test.tsx +241 -0
- package/src/components/resources/renderers/AnalysisRunRenderer.tsx +166 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +31 -2
- package/src/components/resources/renderers/CNPGClusterRenderer.test.tsx +33 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +47 -4
- package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +243 -0
- package/src/components/resources/renderers/CNPGObjectStoreRenderer.test.tsx +99 -0
- package/src/components/resources/renderers/CNPGObjectStoreRenderer.tsx +235 -0
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +23 -1
- package/src/components/resources/renderers/CNPGShared.tsx +27 -0
- package/src/components/resources/renderers/CalicoHostEndpointRenderer.tsx +71 -0
- package/src/components/resources/renderers/CalicoIPPoolRenderer.tsx +32 -0
- package/src/components/resources/renderers/CalicoNetworkPolicyDiagram.test.tsx +205 -0
- package/src/components/resources/renderers/CalicoNetworkPolicyDiagram.tsx +537 -0
- package/src/components/resources/renderers/CalicoNetworkPolicyRenderer.test.tsx +223 -0
- package/src/components/resources/renderers/CalicoNetworkPolicyRenderer.tsx +670 -0
- package/src/components/resources/renderers/CalicoTierRenderer.tsx +19 -0
- package/src/components/resources/renderers/KyvernoCELPolicyRenderers.tsx +13 -6
- package/src/components/resources/renderers/KyvernoExceptionRenderers.tsx +3 -2
- package/src/components/resources/renderers/KyvernoGlobalContextRenderer.test.ts +31 -0
- package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +124 -0
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +28 -2
- package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +303 -0
- package/src/components/resources/renderers/LookupFailureNote.test.tsx +60 -0
- package/src/components/resources/renderers/LookupFailureNote.tsx +42 -0
- package/src/components/resources/renderers/NamespaceRenderer.test.ts +30 -0
- package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -3
- package/src/components/resources/renderers/NetworkPolicyDiagram.tsx +27 -26
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +6 -3
- package/src/components/resources/renderers/PolicyCoverageSection.test.tsx +348 -0
- package/src/components/resources/renderers/PolicyCoverageSection.tsx +939 -0
- package/src/components/resources/renderers/PolicySection.test.tsx +59 -2
- package/src/components/resources/renderers/PolicySection.tsx +32 -5
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +491 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +391 -92
- package/src/components/resources/renderers/VeleroBSLRenderer.test.tsx +374 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +179 -3
- package/src/components/resources/renderers/VeleroBackupRenderer.test.tsx +221 -0
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +119 -6
- package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.test.tsx +69 -0
- package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +96 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.test.tsx +55 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +22 -4
- package/src/components/resources/renderers/VeleroRunMessages.test.tsx +152 -0
- package/src/components/resources/renderers/VeleroRunMessages.tsx +139 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.test.tsx +88 -0
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +16 -6
- package/src/components/resources/renderers/calico-cells.tsx +177 -0
- package/src/components/resources/renderers/cnpg-cells.tsx +100 -0
- package/src/components/resources/renderers/index.ts +16 -1
- package/src/components/resources/renderers/kyverno-cells.tsx +81 -0
- package/src/components/resources/renderers/network-policy-peer-styles.ts +25 -0
- package/src/components/resources/renderers/velero-cells.tsx +29 -7
- package/src/components/resources/renderers/velero-dead-ends.test.tsx +142 -0
- package/src/components/resources/resource-utils-calico.test.ts +215 -0
- package/src/components/resources/resource-utils-calico.ts +261 -0
- package/src/components/resources/resource-utils-cnpg-declarative.test.ts +104 -0
- package/src/components/resources/resource-utils-cnpg-objectstore.test.ts +122 -0
- package/src/components/resources/resource-utils-cnpg.test.ts +54 -0
- package/src/components/resources/resource-utils-cnpg.ts +264 -3
- package/src/components/resources/resource-utils-kyverno-modern.ts +24 -0
- package/src/components/resources/resource-utils-kyverno-queue.test.ts +164 -0
- package/src/components/resources/resource-utils-kyverno-queue.ts +198 -0
- package/src/components/resources/resource-utils-kyverno.ts +43 -7
- package/src/components/resources/resource-utils-velero.test.ts +26 -0
- package/src/components/resources/resource-utils-velero.ts +68 -0
- package/src/components/resources/resource-utils.ts +98 -0
- package/src/components/shared/ResourceActionsBar.tsx +101 -16
- package/src/components/shared/ResourceRendererDispatch.test.tsx +379 -3
- package/src/components/shared/ResourceRendererDispatch.tsx +199 -22
- package/src/components/shared/index.ts +1 -1
- package/src/components/shared/revision-role-badges.test.ts +120 -0
- package/src/components/topology/K8sResourceNode.test.ts +10 -0
- package/src/components/topology/K8sResourceNode.tsx +22 -0
- package/src/components/topology/TopologyFilterSidebar.test.tsx +31 -0
- package/src/components/topology/TopologyFilterSidebar.tsx +5 -0
- package/src/components/topology/TopologyGraph.tsx +6 -6
- package/src/components/topology/TopologySearch.tsx +7 -0
- package/src/components/topology/layout.ts +3 -0
- package/src/components/trace/ReachabilityView.tsx +1 -0
- package/src/components/trace/TracePanel.tsx +13 -2
- package/src/components/trace/reachFixtures.ts +26 -1
- package/src/components/trace/reachGraphModel.test.ts +20 -0
- package/src/components/trace/reachGraphModel.ts +8 -1
- package/src/components/trace/reachOrigins.test.ts +31 -0
- package/src/components/trace/reachOrigins.ts +11 -1
- package/src/components/ui/Badge.tsx +10 -0
- package/src/components/ui/InClusterConsentDialog.tsx +5 -4
- package/src/components/ui/ScheduleValue.test.tsx +80 -0
- package/src/components/ui/ScheduleValue.tsx +85 -0
- package/src/components/ui/SelectMenu.tsx +202 -28
- package/src/components/ui/drawer-components.test.tsx +17 -1
- package/src/components/ui/drawer-components.tsx +9 -2
- package/src/components/ui/index.ts +2 -0
- package/src/components/workload/WorkloadView.tsx +5 -2
- package/src/types/core.ts +36 -4
- package/src/types/policy.ts +153 -2
- package/src/utils/clipboard.test.ts +86 -0
- package/src/utils/clipboard.ts +50 -0
- package/src/utils/extended-resources.ts +4 -7
- package/src/utils/format.test.ts +83 -1
- package/src/utils/format.ts +80 -1
- package/src/utils/inClusterConsent.test.ts +9 -0
- package/src/utils/inClusterConsent.ts +9 -9
- package/src/utils/index.ts +1 -0
- package/src/utils/navigation.test.ts +5 -0
- package/src/utils/navigation.ts +5 -0
- package/src/utils/resource-icons.test.ts +49 -2
- package/src/utils/resource-icons.ts +29 -2
|
@@ -24,6 +24,14 @@ import {
|
|
|
24
24
|
getCNPGPoolerType,
|
|
25
25
|
getCNPGPoolerMode,
|
|
26
26
|
getCNPGPoolerInstances,
|
|
27
|
+
getCNPGObjectStoreStatus,
|
|
28
|
+
getCNPGObjectStoreDestination,
|
|
29
|
+
getCNPGObjectStoreRetention,
|
|
30
|
+
getCNPGObjectStoreRecoveryWindows,
|
|
31
|
+
getCNPGDeclarativeStatus,
|
|
32
|
+
getCNPGDeclarativeCluster,
|
|
33
|
+
getCNPGDeclarativeMessage,
|
|
34
|
+
getCNPGImageCatalogEntries,
|
|
27
35
|
} from '../resource-utils-cnpg'
|
|
28
36
|
|
|
29
37
|
/** Terminal = the operator has stopped reconciling; neighbouring cells are stale. */
|
|
@@ -122,6 +130,98 @@ export function CNPGBackupCell({ resource, column }: { resource: any; column: st
|
|
|
122
130
|
}
|
|
123
131
|
}
|
|
124
132
|
|
|
133
|
+
export function CNPGDeclarativeCell({ resource, column }: { resource: any; column: string }) {
|
|
134
|
+
switch (column) {
|
|
135
|
+
case 'status': {
|
|
136
|
+
const status = getCNPGDeclarativeStatus(resource)
|
|
137
|
+
return <span className={clsx('badge', status.color)}>{status.text}</span>
|
|
138
|
+
}
|
|
139
|
+
case 'cluster':
|
|
140
|
+
return (
|
|
141
|
+
<span className="text-sm text-theme-text-secondary truncate block">
|
|
142
|
+
{getCNPGDeclarativeCluster(resource) || '-'}
|
|
143
|
+
</span>
|
|
144
|
+
)
|
|
145
|
+
case 'target':
|
|
146
|
+
return (
|
|
147
|
+
<span className="text-sm text-theme-text-secondary truncate block">
|
|
148
|
+
{resource?.spec?.name || '-'}
|
|
149
|
+
</span>
|
|
150
|
+
)
|
|
151
|
+
case 'dbname':
|
|
152
|
+
return (
|
|
153
|
+
<span className="text-sm text-theme-text-secondary truncate block">
|
|
154
|
+
{resource?.spec?.dbname || resource?.spec?.owner || '-'}
|
|
155
|
+
</span>
|
|
156
|
+
)
|
|
157
|
+
case 'message':
|
|
158
|
+
return (
|
|
159
|
+
<span
|
|
160
|
+
className="text-sm text-theme-text-secondary truncate block"
|
|
161
|
+
title={getCNPGDeclarativeMessage(resource) || ''}
|
|
162
|
+
>
|
|
163
|
+
{getCNPGDeclarativeMessage(resource) || '-'}
|
|
164
|
+
</span>
|
|
165
|
+
)
|
|
166
|
+
default:
|
|
167
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function CNPGImageCatalogCell({ resource, column }: { resource: any; column: string }) {
|
|
172
|
+
const entries = getCNPGImageCatalogEntries(resource)
|
|
173
|
+
switch (column) {
|
|
174
|
+
case 'majors':
|
|
175
|
+
return (
|
|
176
|
+
<span className="text-sm text-theme-text-secondary">
|
|
177
|
+
{entries.length === 0 ? 'none' : entries.map((e) => e.major).join(', ')}
|
|
178
|
+
</span>
|
|
179
|
+
)
|
|
180
|
+
case 'images':
|
|
181
|
+
return <span className="text-sm text-theme-text-secondary">{String(entries.length)}</span>
|
|
182
|
+
default:
|
|
183
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function CNPGObjectStoreCell({ resource, column }: { resource: any; column: string }) {
|
|
188
|
+
switch (column) {
|
|
189
|
+
case 'status': {
|
|
190
|
+
const status = getCNPGObjectStoreStatus(resource)
|
|
191
|
+
return <span className={clsx('badge', status.color)}>{status.text}</span>
|
|
192
|
+
}
|
|
193
|
+
case 'destination':
|
|
194
|
+
// `block` is load-bearing: an inline span with `truncate` establishes no
|
|
195
|
+
// box, so the column collapses to almost nothing regardless of its width.
|
|
196
|
+
// Every sibling cell that renders a long value pairs the two.
|
|
197
|
+
return (
|
|
198
|
+
<span className="text-sm text-theme-text-secondary truncate block">
|
|
199
|
+
{getCNPGObjectStoreDestination(resource)}
|
|
200
|
+
</span>
|
|
201
|
+
)
|
|
202
|
+
case 'retention':
|
|
203
|
+
return (
|
|
204
|
+
<span className="text-sm text-theme-text-secondary">
|
|
205
|
+
{getCNPGObjectStoreRetention(resource) || '-'}
|
|
206
|
+
</span>
|
|
207
|
+
)
|
|
208
|
+
case 'servers': {
|
|
209
|
+
const windows = getCNPGObjectStoreRecoveryWindows(resource)
|
|
210
|
+
if (windows.length === 0) {
|
|
211
|
+
return <span className="text-sm text-theme-text-tertiary">none</span>
|
|
212
|
+
}
|
|
213
|
+
const failing = windows.filter((w) => w.failingSinceLastSuccess).length
|
|
214
|
+
return (
|
|
215
|
+
<span className="text-sm text-theme-text-secondary">
|
|
216
|
+
{failing > 0 ? `${windows.length} (${failing} failing)` : String(windows.length)}
|
|
217
|
+
</span>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
default:
|
|
221
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
125
225
|
export function CNPGScheduledBackupCell({ resource, column }: { resource: any; column: string }) {
|
|
126
226
|
switch (column) {
|
|
127
227
|
case 'status': {
|
|
@@ -4,6 +4,12 @@ export * from './argo-cells'
|
|
|
4
4
|
export * from './CertificateRenderer'
|
|
5
5
|
export * from './CertificateRequestRenderer'
|
|
6
6
|
export * from './certmanager-cells'
|
|
7
|
+
export * from './CalicoHostEndpointRenderer'
|
|
8
|
+
export * from './CalicoIPPoolRenderer'
|
|
9
|
+
export * from './CalicoNetworkPolicyRenderer'
|
|
10
|
+
export * from './CalicoNetworkPolicyDiagram'
|
|
11
|
+
export * from './CalicoTierRenderer'
|
|
12
|
+
export * from './calico-cells'
|
|
7
13
|
export * from './ChallengeRenderer'
|
|
8
14
|
export * from './ClusterComplianceReportRenderer'
|
|
9
15
|
export * from './ClusterExternalSecretRenderer'
|
|
@@ -74,6 +80,7 @@ export * from './KyvernoPolicyShared'
|
|
|
74
80
|
export * from './KyvernoCELPolicyRenderers'
|
|
75
81
|
export * from './KyvernoExceptionRenderers'
|
|
76
82
|
export * from './LeaseRenderer'
|
|
83
|
+
export * from './LookupFailureNote'
|
|
77
84
|
export * from './NetworkPolicyRenderer'
|
|
78
85
|
export * from './NetworkPolicyDiagram'
|
|
79
86
|
export * from './CiliumNetworkPolicyRenderer'
|
|
@@ -94,6 +101,7 @@ export * from './ReplicaSetRenderer'
|
|
|
94
101
|
export * from './RoleBindingRenderer'
|
|
95
102
|
export * from './RoleRenderer'
|
|
96
103
|
export * from './RolloutRenderer'
|
|
104
|
+
export * from './AnalysisRunRenderer'
|
|
97
105
|
export * from './RuntimeClassRenderer'
|
|
98
106
|
export * from './SbomReportRenderer'
|
|
99
107
|
export * from './SealedSecretRenderer'
|
|
@@ -109,10 +117,12 @@ export * from './trivy-cells'
|
|
|
109
117
|
export * from './trivy-shared'
|
|
110
118
|
export * from './velero-cells'
|
|
111
119
|
export * from './VeleroBackupRenderer'
|
|
120
|
+
export * from './VeleroRunMessages'
|
|
112
121
|
export * from './VeleroBSLRenderer'
|
|
113
122
|
export * from './VeleroRestoreRenderer'
|
|
114
123
|
export * from './VeleroScheduleRenderer'
|
|
115
124
|
export * from './VeleroVSLRenderer'
|
|
125
|
+
export * from './VeleroBackupRepositoryRenderer'
|
|
116
126
|
export * from './VPARenderer'
|
|
117
127
|
export * from './VulnerabilityReportRenderer'
|
|
118
128
|
export * from './WebhookConfigRenderer'
|
|
@@ -160,4 +170,9 @@ export * from './ResourceSliceRenderer'
|
|
|
160
170
|
export * from './nvidia-cells'
|
|
161
171
|
export * from './NvidiaClusterPolicyRenderer'
|
|
162
172
|
export * from './NvidiaDriverRenderer'
|
|
163
|
-
export
|
|
173
|
+
export * from './PolicySection'
|
|
174
|
+
export * from './PolicyCoverageSection'
|
|
175
|
+
export * from './KyvernoGlobalContextRenderer'
|
|
176
|
+
export * from './KyvernoQueueRenderers'
|
|
177
|
+
export * from './CNPGObjectStoreRenderer'
|
|
178
|
+
export * from './CNPGDeclarativeRenderer'
|
|
@@ -9,6 +9,16 @@ import {
|
|
|
9
9
|
getKyvernoPolicyAction,
|
|
10
10
|
getKyvernoPolicyRuleCount,
|
|
11
11
|
} from '../resource-utils-kyverno'
|
|
12
|
+
import {
|
|
13
|
+
getKyvernoRequestState,
|
|
14
|
+
getKyvernoRequestType,
|
|
15
|
+
getKyvernoRequestPolicy,
|
|
16
|
+
getKyvernoRequestTriggers,
|
|
17
|
+
getKyvernoReportStatus,
|
|
18
|
+
getKyvernoReportSubject,
|
|
19
|
+
getKyvernoReportSource,
|
|
20
|
+
getKyvernoReportSummary,
|
|
21
|
+
} from '../resource-utils-kyverno-queue'
|
|
12
22
|
|
|
13
23
|
export function PolicyReportCell({ resource, column }: { resource: any; column: string }) {
|
|
14
24
|
switch (column) {
|
|
@@ -92,3 +102,74 @@ export function ClusterPolicyCell({ resource, column }: { resource: any; column:
|
|
|
92
102
|
// Same rendering logic as KyvernoPolicy
|
|
93
103
|
return <KyvernoPolicyCell resource={resource} column={column} />
|
|
94
104
|
}
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// UpdateRequest / EphemeralReport
|
|
108
|
+
// ============================================================================
|
|
109
|
+
|
|
110
|
+
export function KyvernoUpdateRequestCell({ resource, column }: { resource: any; column: string }) {
|
|
111
|
+
switch (column) {
|
|
112
|
+
case 'status': {
|
|
113
|
+
const state = getKyvernoRequestState(resource)
|
|
114
|
+
return <span className={clsx('badge', state.color)}>{state.text}</span>
|
|
115
|
+
}
|
|
116
|
+
case 'type': {
|
|
117
|
+
const t = getKyvernoRequestType(resource)
|
|
118
|
+
return <span className="text-sm text-theme-text-secondary">{t === 'unknown' ? '-' : t}</span>
|
|
119
|
+
}
|
|
120
|
+
case 'policy':
|
|
121
|
+
return (
|
|
122
|
+
<span className="text-sm text-theme-text-secondary truncate block" title={getKyvernoRequestPolicy(resource)}>
|
|
123
|
+
{getKyvernoRequestPolicy(resource) || '-'}
|
|
124
|
+
</span>
|
|
125
|
+
)
|
|
126
|
+
case 'triggers': {
|
|
127
|
+
// A generate request carries every trigger it was queued for, so the row
|
|
128
|
+
// has to say "and N more" rather than pretend it is about one resource.
|
|
129
|
+
const triggers = getKyvernoRequestTriggers(resource)
|
|
130
|
+
if (triggers.length === 0) return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
131
|
+
const first = triggers[0]
|
|
132
|
+
const label = `${first.kind ?? ''}/${first.name ?? ''}`
|
|
133
|
+
return (
|
|
134
|
+
<span className="text-sm text-theme-text-secondary truncate block" title={label}>
|
|
135
|
+
{triggers.length === 1 ? label : `${label} +${triggers.length - 1}`}
|
|
136
|
+
</span>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
default:
|
|
140
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function KyvernoEphemeralReportCell({ resource, column }: { resource: any; column: string }) {
|
|
145
|
+
switch (column) {
|
|
146
|
+
case 'status': {
|
|
147
|
+
const status = getKyvernoReportStatus(resource)
|
|
148
|
+
return <span className={clsx('badge', status.color)}>{status.text}</span>
|
|
149
|
+
}
|
|
150
|
+
case 'subject': {
|
|
151
|
+
const s = getKyvernoReportSubject(resource)
|
|
152
|
+
if (!s?.kind) return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
153
|
+
const label = s.name ? `${s.kind}/${s.name}` : `${s.kind} (deleted)`
|
|
154
|
+
return (
|
|
155
|
+
<span className="text-sm text-theme-text-secondary truncate block" title={label}>
|
|
156
|
+
{label}
|
|
157
|
+
</span>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
case 'source':
|
|
161
|
+
return (
|
|
162
|
+
<span className="text-sm text-theme-text-secondary truncate block">
|
|
163
|
+
{(getKyvernoReportSource(resource) || '-').replace(/-/g, ' ')}
|
|
164
|
+
</span>
|
|
165
|
+
)
|
|
166
|
+
case 'results':
|
|
167
|
+
return (
|
|
168
|
+
<span className="text-sm text-theme-text-secondary">
|
|
169
|
+
{String(getKyvernoReportSummary(resource).total)}
|
|
170
|
+
</span>
|
|
171
|
+
)
|
|
172
|
+
default:
|
|
173
|
+
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type NetworkPolicyPeerType =
|
|
2
|
+
| "pod"
|
|
3
|
+
| "namespace"
|
|
4
|
+
| "cidr"
|
|
5
|
+
| "all"
|
|
6
|
+
| "deny"
|
|
7
|
+
| "combined";
|
|
8
|
+
|
|
9
|
+
export const NETWORK_POLICY_PEER_STYLES = {
|
|
10
|
+
pod: "border-emerald-500/30 bg-emerald-500/8",
|
|
11
|
+
namespace: "border-sky-500/30 bg-sky-500/8",
|
|
12
|
+
cidr: "border-amber-500/30 bg-amber-500/8",
|
|
13
|
+
combined: "border-emerald-500/30 bg-emerald-500/8",
|
|
14
|
+
all: "border-theme-border bg-theme-elevated/50",
|
|
15
|
+
deny: "border-red-500/30 bg-red-500/8",
|
|
16
|
+
} as const satisfies Record<NetworkPolicyPeerType, string>;
|
|
17
|
+
|
|
18
|
+
export const NETWORK_POLICY_PEER_DOTS = {
|
|
19
|
+
pod: "bg-emerald-500",
|
|
20
|
+
namespace: "bg-sky-500",
|
|
21
|
+
cidr: "bg-amber-500",
|
|
22
|
+
combined: "bg-emerald-500",
|
|
23
|
+
all: "bg-theme-text-tertiary",
|
|
24
|
+
deny: "bg-red-500",
|
|
25
|
+
} as const satisfies Record<NetworkPolicyPeerType, string>;
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
getRestoreIncludedNamespaces,
|
|
17
17
|
getRestoreDuration,
|
|
18
18
|
getRestoreErrors,
|
|
19
|
+
getRestoreWarnings,
|
|
19
20
|
getScheduleStatus,
|
|
20
21
|
getSchedulePaused,
|
|
21
22
|
getScheduleCronInfo,
|
|
@@ -37,6 +38,10 @@ import {
|
|
|
37
38
|
// whenever the two differ. Without this the three collapsed partial-failure
|
|
38
39
|
// phases would be unreachable outside the YAML tab, and the label would stop
|
|
39
40
|
// being a display choice and start being a loss of information.
|
|
41
|
+
// Foreground colours here carry a light counterpart as well as the dark one.
|
|
42
|
+
// A bare `text-*-400` is tuned for the dark ground and washes out on the light
|
|
43
|
+
// one — the same defect rbac-badges.ts was created to fix, and these cells sit
|
|
44
|
+
// in a table next to badges that already do this correctly.
|
|
40
45
|
export function VeleroPhaseValue({
|
|
41
46
|
status,
|
|
42
47
|
phase,
|
|
@@ -84,7 +89,7 @@ export function BackupCell({ resource, column }: { resource: any; column: string
|
|
|
84
89
|
case 'expiry': {
|
|
85
90
|
const exp = getBackupExpiry(resource)
|
|
86
91
|
const isExpired = exp === 'Expired'
|
|
87
|
-
return <span className={clsx('text-sm', isExpired ? 'text-red-400' : 'text-theme-text-secondary')}>{exp}</span>
|
|
92
|
+
return <span className={clsx('text-sm', isExpired ? 'text-red-600 dark:text-red-400' : 'text-theme-text-secondary')}>{exp}</span>
|
|
88
93
|
}
|
|
89
94
|
case 'errors': {
|
|
90
95
|
const errors = getBackupErrors(resource)
|
|
@@ -94,9 +99,9 @@ export function BackupCell({ resource, column }: { resource: any; column: string
|
|
|
94
99
|
}
|
|
95
100
|
return (
|
|
96
101
|
<span className="text-sm">
|
|
97
|
-
{errors > 0 && <span className="text-red-400">{errors}E</span>}
|
|
102
|
+
{errors > 0 && <span className="text-red-600 dark:text-red-400">{errors}E</span>}
|
|
98
103
|
{errors > 0 && warnings > 0 && <span className="text-theme-text-tertiary"> / </span>}
|
|
99
|
-
{warnings > 0 && <span className="text-
|
|
104
|
+
{warnings > 0 && <span className="text-amber-600 dark:text-amber-400">{warnings}W</span>}
|
|
100
105
|
</span>
|
|
101
106
|
)
|
|
102
107
|
}
|
|
@@ -128,11 +133,21 @@ export function RestoreCell({ resource, column }: { resource: any; column: strin
|
|
|
128
133
|
return <span className="text-sm text-theme-text-secondary">{dur}</span>
|
|
129
134
|
}
|
|
130
135
|
case 'errors': {
|
|
136
|
+
// Errors AND warnings, the same shape the Backup column uses. A restore
|
|
137
|
+
// that reported only warnings showed "-" here while carrying a non-zero
|
|
138
|
+
// count on the object, which is the row a reader scans to decide whether
|
|
139
|
+
// the restore is worth opening.
|
|
131
140
|
const errors = getRestoreErrors(resource)
|
|
132
|
-
|
|
141
|
+
const warnings = getRestoreWarnings(resource)
|
|
142
|
+
if (errors === 0 && warnings === 0) {
|
|
133
143
|
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
134
144
|
}
|
|
135
|
-
return
|
|
145
|
+
return (
|
|
146
|
+
<span className="text-sm flex gap-1.5">
|
|
147
|
+
{errors > 0 && <span className="text-red-600 dark:text-red-400">{errors}E</span>}
|
|
148
|
+
{warnings > 0 && <span className="text-amber-600 dark:text-amber-400">{warnings}W</span>}
|
|
149
|
+
</span>
|
|
150
|
+
)
|
|
136
151
|
}
|
|
137
152
|
default:
|
|
138
153
|
return <span className="text-sm text-theme-text-tertiary">-</span>
|
|
@@ -157,7 +172,14 @@ export function ScheduleCell({ resource, column }: { resource: any; column: stri
|
|
|
157
172
|
</span>
|
|
158
173
|
{alsoPaused && (
|
|
159
174
|
<Tooltip content="Also paused — this schedule will not run until it is resumed">
|
|
160
|
-
|
|
175
|
+
<>
|
|
176
|
+
<Pause className="w-3 h-3 shrink-0 text-amber-600 dark:text-amber-400" aria-hidden="true" />
|
|
177
|
+
{/* The badge beside this says "Rejected", so without a label the
|
|
178
|
+
row reads as a schedule that is merely broken rather than one
|
|
179
|
+
that is also switched off — and the tooltip carrying that
|
|
180
|
+
second fact needs a pointer to reach. */}
|
|
181
|
+
<span className="sr-only">Also paused — this schedule will not run until it is resumed</span>
|
|
182
|
+
</>
|
|
161
183
|
</Tooltip>
|
|
162
184
|
)}
|
|
163
185
|
</span>
|
|
@@ -271,7 +293,7 @@ export function BackupStorageLocationCell({ resource, column }: { resource: any;
|
|
|
271
293
|
}
|
|
272
294
|
case 'default': {
|
|
273
295
|
const isDefault = getBSLDefault(resource)
|
|
274
|
-
return <span className={clsx('text-sm', isDefault ? 'text-blue-400' : 'text-theme-text-tertiary')}>{isDefault ? 'Yes' : '-'}</span>
|
|
296
|
+
return <span className={clsx('text-sm', isDefault ? 'text-blue-600 dark:text-blue-400' : 'text-theme-text-tertiary')}>{isDefault ? 'Yes' : '-'}</span>
|
|
275
297
|
}
|
|
276
298
|
case 'lastValidation': {
|
|
277
299
|
const lastVal = getBSLLastValidation(resource)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import { VeleroBackupRenderer } from './VeleroBackupRenderer'
|
|
4
|
+
import { VeleroRestoreRenderer } from './VeleroRestoreRenderer'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Every screen has to end somewhere the reader can act.
|
|
8
|
+
*
|
|
9
|
+
* These two pages each name another object as the reason the reader should care
|
|
10
|
+
* — a storage location that is holding a backup hostage, and the backup a
|
|
11
|
+
* restore came from — and each rendered that name as plain text. The page said
|
|
12
|
+
* "go look at dr-replica" and gave no way to get there.
|
|
13
|
+
*
|
|
14
|
+
* The link has to disappear cleanly when the host does not wire navigation:
|
|
15
|
+
* library consumers render these renderers without it, and a button that goes
|
|
16
|
+
* nowhere is worse than text.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const backup = (location: string) => ({
|
|
20
|
+
metadata: { name: 'nightly', namespace: 'velero' },
|
|
21
|
+
spec: { storageLocation: location },
|
|
22
|
+
status: { phase: 'Completed' },
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const restore = {
|
|
26
|
+
metadata: { name: 'recovery', namespace: 'velero' },
|
|
27
|
+
spec: { backupName: 'nightly' },
|
|
28
|
+
status: { phase: 'Completed' },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('a backup that blames its storage location', () => {
|
|
32
|
+
it('lets the reader get to the location', () => {
|
|
33
|
+
const html = renderToString(
|
|
34
|
+
<VeleroBackupRenderer
|
|
35
|
+
data={backup('dr-replica')}
|
|
36
|
+
storageLocationPhase="Unavailable"
|
|
37
|
+
onNavigate={vi.fn()}
|
|
38
|
+
/>,
|
|
39
|
+
)
|
|
40
|
+
// The name is a control, not a label.
|
|
41
|
+
expect(html).toMatch(/<button[^>]*>dr-replica<\/button>/)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('renders the location as text when navigation is not wired', () => {
|
|
45
|
+
const html = renderToString(
|
|
46
|
+
<VeleroBackupRenderer data={backup('dr-replica')} storageLocationPhase="Unavailable" />,
|
|
47
|
+
)
|
|
48
|
+
expect(html).toContain('dr-replica')
|
|
49
|
+
expect(html).not.toMatch(/<button[^>]*>dr-replica<\/button>/)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// The link is the way to the location whether or not it is broken — a healthy
|
|
53
|
+
// one is still the thing you check when deciding where a backup lives.
|
|
54
|
+
it('links the location even when it is healthy', () => {
|
|
55
|
+
const html = renderToString(
|
|
56
|
+
<VeleroBackupRenderer data={backup('default')} storageLocationPhase="Available" onNavigate={vi.fn()} />,
|
|
57
|
+
)
|
|
58
|
+
expect(html).toMatch(/<button[^>]*>default<\/button>/)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('a restore that names its backup', () => {
|
|
63
|
+
it('lets the reader get to the backup it restored from', () => {
|
|
64
|
+
const html = renderToString(<VeleroRestoreRenderer data={restore} onNavigate={vi.fn()} />)
|
|
65
|
+
expect(html).toMatch(/<button[^>]*>nightly<\/button>/)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('renders the backup as text when navigation is not wired', () => {
|
|
69
|
+
const html = renderToString(<VeleroRestoreRenderer data={restore} />)
|
|
70
|
+
expect(html).toContain('nightly')
|
|
71
|
+
expect(html).not.toMatch(/<button[^>]*>nightly<\/button>/)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The stalled-run issue says a run is "past the 1m Velero allows for one
|
|
77
|
+
* operation". That budget is spec.itemOperationTimeout, and the Options section
|
|
78
|
+
* listed every sibling spec option except it — so the page quoted a number whose
|
|
79
|
+
* source it did not show, next to the place that source belongs.
|
|
80
|
+
*/
|
|
81
|
+
describe('the budget the stall message measures against', () => {
|
|
82
|
+
it('shows the timeout the message quotes', () => {
|
|
83
|
+
const html = renderToString(
|
|
84
|
+
<VeleroBackupRenderer
|
|
85
|
+
data={{
|
|
86
|
+
metadata: { name: 'stuck', namespace: 'velero' },
|
|
87
|
+
spec: { storageLocation: 'default', itemOperationTimeout: '1m0s' },
|
|
88
|
+
status: { phase: 'InProgress' },
|
|
89
|
+
}}
|
|
90
|
+
/>,
|
|
91
|
+
)
|
|
92
|
+
expect(html).toContain('Item Operation Timeout')
|
|
93
|
+
expect(html).toContain('1m0s')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// Unset is the common case, and it is not "none" — a default applies. But the
|
|
97
|
+
// number is not ours to print: Velero's built-in four hours can be overridden
|
|
98
|
+
// on the controller, so a page that renders "4h" would be stating an install
|
|
99
|
+
// setting it cannot read.
|
|
100
|
+
it('does not invent a number when the field is unset', () => {
|
|
101
|
+
const html = renderToString(
|
|
102
|
+
<VeleroBackupRenderer
|
|
103
|
+
data={{
|
|
104
|
+
metadata: { name: 'plain', namespace: 'velero' },
|
|
105
|
+
spec: { storageLocation: 'default' },
|
|
106
|
+
status: { phase: 'InProgress' },
|
|
107
|
+
}}
|
|
108
|
+
/>,
|
|
109
|
+
)
|
|
110
|
+
// 'default' is this file's own word for "unset, and something else decides".
|
|
111
|
+
expect(html).toContain('default')
|
|
112
|
+
expect(html).not.toContain('4h0m0s')
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Claims the phase does not support.
|
|
118
|
+
*
|
|
119
|
+
* The warning banner fired on any run with warnings that was not failed or
|
|
120
|
+
* partially failed — which includes runs still in flight — and said the run
|
|
121
|
+
* "completed". A count taken mid-run is not a final count, and saying otherwise
|
|
122
|
+
* is the same defect as calling a Completed backup restorable.
|
|
123
|
+
*/
|
|
124
|
+
describe('a run that has warnings but has not finished', () => {
|
|
125
|
+
const withWarnings = (phase: string) => ({
|
|
126
|
+
metadata: { name: 'w', namespace: 'velero' },
|
|
127
|
+
spec: { storageLocation: 'default' },
|
|
128
|
+
status: { phase, warnings: 2 },
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
it('does not say a run in flight completed', () => {
|
|
132
|
+
const html = renderToString(<VeleroBackupRenderer data={withWarnings('InProgress')} />)
|
|
133
|
+
expect(html).not.toMatch(/completed, with/)
|
|
134
|
+
expect(html).toContain('not a final count')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('still says completed when it did', () => {
|
|
138
|
+
const html = renderToString(<VeleroBackupRenderer data={withWarnings('Completed')} />)
|
|
139
|
+
expect(html).toContain('completed, with 2 warning(s)')
|
|
140
|
+
expect(html).not.toContain('not a final count')
|
|
141
|
+
})
|
|
142
|
+
})
|