@skyhook-io/k8s-ui 1.10.4 → 1.11.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 +5 -5
- package/src/components/applications/ApplicationDetail.test.tsx +46 -0
- package/src/components/applications/ApplicationDetail.tsx +14 -15
- package/src/components/issues/IssuesView.tsx +9 -0
- package/src/components/issues/diagnostic.ts +3 -0
- package/src/components/issues/issues.test.ts +11 -0
- package/src/components/issues/severity.ts +3 -0
- package/src/components/issues/types.ts +3 -0
- package/src/components/resources/ResourcesSidebar.tsx +1 -1
- package/src/components/resources/ResourcesView.tsx +259 -30
- package/src/components/resources/index.ts +2 -0
- package/src/components/resources/kyverno-cell-gating.test.ts +93 -0
- package/src/components/resources/kyverno-modern-posture.test.ts +290 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.test.tsx +85 -0
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +109 -9
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +8 -4
- package/src/components/resources/renderers/EventRenderer.test.tsx +23 -0
- package/src/components/resources/renderers/EventRenderer.tsx +10 -18
- package/src/components/resources/renderers/KyvernoCELPolicyRenderers.tsx +317 -0
- package/src/components/resources/renderers/KyvernoExceptionRenderers.tsx +264 -0
- package/src/components/resources/renderers/KyvernoPolicyShared.tsx +212 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +2 -4
- package/src/components/resources/renderers/VeleroBackupRenderer.tsx +32 -9
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +30 -9
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +12 -6
- package/src/components/resources/renderers/badge-no-handrolled.test.tsx +1 -1
- package/src/components/resources/renderers/cnpg-cells.tsx +37 -2
- package/src/components/resources/renderers/index.ts +4 -0
- package/src/components/resources/renderers/kyverno-modern-cells.tsx +152 -0
- package/src/components/resources/renderers/velero-cells.tsx +128 -9
- package/src/components/resources/renderers/velero-phase-recovery.test.tsx +79 -0
- package/src/components/resources/resource-utils-cnpg.golden.test.ts +112 -0
- package/src/components/resources/resource-utils-cnpg.test.ts +527 -1
- package/src/components/resources/resource-utils-cnpg.ts +373 -56
- package/src/components/resources/resource-utils-kyverno-exceptions.ts +182 -0
- package/src/components/resources/resource-utils-kyverno-modern.ts +588 -0
- package/src/components/resources/resource-utils-velero.test.ts +237 -0
- package/src/components/resources/resource-utils-velero.ts +214 -22
- package/src/components/resources/resource-utils.ts +70 -3
- package/src/components/shared/ResourceActionsBar.tsx +3 -1
- package/src/components/shared/ResourceRendererDispatch.test.tsx +119 -1
- package/src/components/shared/ResourceRendererDispatch.tsx +116 -22
- package/src/components/topology/K8sResourceNode.tsx +11 -0
- package/src/components/topology/TopologyGraph.tsx +85 -5
- package/src/components/trace/ReachabilityGraph.tsx +517 -0
- package/src/components/trace/ReachabilityView.tsx +1173 -0
- package/src/components/trace/TracePanel.test.ts +172 -0
- package/src/components/trace/TracePanel.tsx +558 -0
- package/src/components/trace/TraceSummary.test.ts +213 -0
- package/src/components/trace/TraceSummary.tsx +168 -0
- package/src/components/trace/inClusterSummary.test.ts +98 -0
- package/src/components/trace/inClusterSummary.ts +81 -0
- package/src/components/trace/index.ts +19 -0
- package/src/components/trace/podReach.test.ts +71 -0
- package/src/components/trace/podReach.ts +37 -0
- package/src/components/trace/probe-display.test.ts +107 -0
- package/src/components/trace/probe-display.ts +37 -0
- package/src/components/trace/problemRows.test.ts +107 -0
- package/src/components/trace/reachFixtures.ts +261 -0
- package/src/components/trace/reachGraphModel.test.ts +1423 -0
- package/src/components/trace/reachGraphModel.ts +1521 -0
- package/src/components/trace/reachInspector.test.ts +892 -0
- package/src/components/trace/reachInspector.ts +797 -0
- package/src/components/trace/reachMarks.test.ts +698 -0
- package/src/components/trace/reachMarks.ts +623 -0
- package/src/components/trace/reachOrigins.test.ts +392 -0
- package/src/components/trace/reachOrigins.ts +449 -0
- package/src/components/trace/reachVerdict.test.ts +537 -0
- package/src/components/trace/reachVerdict.ts +601 -0
- package/src/components/trace/traceFingerprint.test.ts +214 -0
- package/src/components/trace/traceFingerprint.ts +122 -0
- package/src/components/trace/traceToSubgraph.test.ts +623 -0
- package/src/components/trace/traceToSubgraph.ts +463 -0
- package/src/components/trace/types.ts +414 -0
- package/src/components/ui/Badge.tsx +36 -0
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +12 -1
- package/src/components/ui/InClusterConsentDialog.test.tsx +83 -0
- package/src/components/ui/InClusterConsentDialog.tsx +141 -0
- package/src/components/ui/index.ts +1 -0
- package/src/components/workload/WorkloadView.tsx +58 -5
- package/src/components/workload/index.ts +1 -1
- package/src/components/workload/reachabilityTab.test.ts +47 -0
- package/src/index.ts +5 -0
- package/src/theme/components.css +64 -0
- package/src/theme/variables.css +5 -0
- package/src/types/core.ts +8 -0
- package/src/utils/api-resources.ts +2 -0
- package/src/utils/application-history.test.ts +1 -1
- package/src/utils/applications.test.ts +63 -1
- package/src/utils/applications.ts +27 -0
- package/src/utils/inClusterConsent.test.ts +109 -0
- package/src/utils/inClusterConsent.ts +72 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/navigation.test.ts +61 -0
- package/src/utils/navigation.ts +11 -9
- package/src/utils/pluralize.test.ts +80 -1
- package/src/utils/pluralize.ts +34 -0
- package/src/utils/resource-icons.test.ts +33 -0
- package/src/utils/resource-icons.ts +65 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AlertTriangle, Info, Clock, Target, Server, Hash } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
|
+
import { Badge } from '../../ui/Badge'
|
|
3
4
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
4
5
|
import { formatAge } from '../resource-utils'
|
|
5
6
|
import type { NavigateToResource } from '../../../utils/navigation'
|
|
@@ -20,35 +21,29 @@ export function EventRenderer({ data, onNavigate }: EventRendererProps) {
|
|
|
20
21
|
const source = data.source || {}
|
|
21
22
|
const firstTimestamp = data.firstTimestamp
|
|
22
23
|
const lastTimestamp = data.lastTimestamp || data.metadata?.creationTimestamp
|
|
24
|
+
const statusClass = isWarning ? 'status-degraded' : 'status-neutral'
|
|
25
|
+
const badgeSeverity = isWarning ? 'warning' : 'info'
|
|
23
26
|
|
|
24
27
|
return (
|
|
25
28
|
<>
|
|
26
29
|
{/* Event Type Banner */}
|
|
27
30
|
<div className={clsx(
|
|
28
31
|
'mb-4 p-4 rounded-lg border',
|
|
29
|
-
|
|
30
|
-
? 'bg-amber-500/10 border-amber-500/30'
|
|
31
|
-
: 'bg-blue-500/10 border-blue-500/30'
|
|
32
|
+
statusClass,
|
|
32
33
|
)}>
|
|
33
34
|
<div className="flex items-start gap-3">
|
|
34
35
|
{isWarning ? (
|
|
35
|
-
<AlertTriangle className="w-5 h-5
|
|
36
|
+
<AlertTriangle className="w-5 h-5 mt-0.5 shrink-0" />
|
|
36
37
|
) : (
|
|
37
|
-
<Info className="w-5 h-5
|
|
38
|
+
<Info className="w-5 h-5 mt-0.5 shrink-0" />
|
|
38
39
|
)}
|
|
39
40
|
<div className="flex-1 min-w-0">
|
|
40
41
|
<div className="flex items-center gap-2 mb-1">
|
|
41
|
-
<
|
|
42
|
-
'badge',
|
|
43
|
-
isWarning ? 'bg-amber-500/20 text-amber-400' : 'bg-blue-500/20 text-blue-400'
|
|
44
|
-
)}>
|
|
42
|
+
<Badge severity={badgeSeverity}>
|
|
45
43
|
{eventType}
|
|
46
|
-
</
|
|
44
|
+
</Badge>
|
|
47
45
|
{reason && (
|
|
48
|
-
<span className=
|
|
49
|
-
'text-sm font-semibold',
|
|
50
|
-
isWarning ? 'text-amber-300' : 'text-blue-300'
|
|
51
|
-
)}>
|
|
46
|
+
<span className="text-sm font-semibold">
|
|
52
47
|
{reason}
|
|
53
48
|
</span>
|
|
54
49
|
)}
|
|
@@ -59,10 +54,7 @@ export function EventRenderer({ data, onNavigate }: EventRendererProps) {
|
|
|
59
54
|
)}
|
|
60
55
|
</div>
|
|
61
56
|
{message && (
|
|
62
|
-
<p className=
|
|
63
|
-
'text-sm break-all',
|
|
64
|
-
isWarning ? 'text-amber-200/90' : 'text-blue-200/90'
|
|
65
|
-
)}>
|
|
57
|
+
<p className="text-sm break-all">
|
|
66
58
|
{message}
|
|
67
59
|
</p>
|
|
68
60
|
)}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
// Renderers for Kyverno's modern CEL policy family (policies.kyverno.io),
|
|
2
|
+
// stabilized in Kyverno 1.17/1.18 and the family that survives the removal of
|
|
3
|
+
// kyverno.io Policy/ClusterPolicy planned for 1.20.
|
|
4
|
+
//
|
|
5
|
+
// One renderer per behavioural family, each sharing the posture header and
|
|
6
|
+
// match-scope sections from KyvernoPolicyShared. The five specs overlap far
|
|
7
|
+
// less than their names suggest — DeletingPolicy has no failurePolicy,
|
|
8
|
+
// evaluation or webhookConfiguration at all and is scheduled rather than
|
|
9
|
+
// admission-driven — so a merged renderer would be mostly branches.
|
|
10
|
+
//
|
|
11
|
+
// Each Namespaced* twin renders through its cluster-scoped counterpart: same
|
|
12
|
+
// spec, narrower scope, and the scope is already conveyed by the namespace in
|
|
13
|
+
// the resource header.
|
|
14
|
+
|
|
15
|
+
import { FileWarning, ShieldCheck, Wand2, Sparkles, Trash2, Clock, Fingerprint, ScrollText } from 'lucide-react'
|
|
16
|
+
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
17
|
+
import {
|
|
18
|
+
KyvernoEvaluationSection,
|
|
19
|
+
KyvernoExpressionList,
|
|
20
|
+
KyvernoMatchScopeSection,
|
|
21
|
+
KyvernoPostureHeader,
|
|
22
|
+
KyvernoVariablesSection,
|
|
23
|
+
} from './KyvernoPolicyShared'
|
|
24
|
+
import {
|
|
25
|
+
getKyvernoAttestations,
|
|
26
|
+
getKyvernoAttestors,
|
|
27
|
+
getKyvernoDeletionConditions,
|
|
28
|
+
getKyvernoDeletionPropagationPolicy,
|
|
29
|
+
getKyvernoGenerationSettings,
|
|
30
|
+
getKyvernoGenerations,
|
|
31
|
+
getKyvernoImageExtractors,
|
|
32
|
+
getKyvernoImageReferences,
|
|
33
|
+
getKyvernoMutations,
|
|
34
|
+
getKyvernoPolicyConditions,
|
|
35
|
+
getKyvernoReinvocationPolicy,
|
|
36
|
+
getKyvernoSchedule,
|
|
37
|
+
getKyvernoValidations,
|
|
38
|
+
type KyvernoCELValidation,
|
|
39
|
+
} from '../resource-utils-kyverno-modern'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The CEL rules themselves, each with the message an operator sees when it
|
|
43
|
+
* fails. Expression and message belong together — a rule without its message
|
|
44
|
+
* is unreadable, and a message without its rule is unverifiable.
|
|
45
|
+
*/
|
|
46
|
+
function ValidationsSection({ validations }: { validations: KyvernoCELValidation[] }) {
|
|
47
|
+
if (validations.length === 0) return null
|
|
48
|
+
return (
|
|
49
|
+
<Section title={`Validations (${validations.length})`} icon={FileWarning}>
|
|
50
|
+
<div className="space-y-2">
|
|
51
|
+
{validations.map((v, i) => (
|
|
52
|
+
<div key={i} className="card-inner-lg">
|
|
53
|
+
<pre className="text-xs text-theme-text-primary font-mono whitespace-pre-wrap break-all">
|
|
54
|
+
{v.expression}
|
|
55
|
+
</pre>
|
|
56
|
+
{(v.message || v.messageExpression) && (
|
|
57
|
+
<div className="mt-2 text-xs text-theme-text-secondary">
|
|
58
|
+
{v.message || <span className="font-mono">{v.messageExpression}</span>}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
{v.reason && (
|
|
62
|
+
<div className="mt-1">
|
|
63
|
+
<span className="badge status-neutral">{v.reason}</span>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
</Section>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function KyvernoValidatingPolicyRenderer({ data }: { data: any }) {
|
|
74
|
+
return (
|
|
75
|
+
<div className="space-y-4">
|
|
76
|
+
<KyvernoPostureHeader data={data} family="validating" />
|
|
77
|
+
<ValidationsSection validations={getKyvernoValidations(data)} />
|
|
78
|
+
<KyvernoMatchScopeSection data={data} />
|
|
79
|
+
<KyvernoVariablesSection data={data} />
|
|
80
|
+
<KyvernoEvaluationSection data={data} />
|
|
81
|
+
<ConditionsSection conditions={getKyvernoPolicyConditions(data)} />
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function KyvernoImageValidatingPolicyRenderer({ data }: { data: any }) {
|
|
87
|
+
const attestors = getKyvernoAttestors(data)
|
|
88
|
+
const attestations = getKyvernoAttestations(data)
|
|
89
|
+
const imageRefs = getKyvernoImageReferences(data)
|
|
90
|
+
const extractors = getKyvernoImageExtractors(data)
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="space-y-4">
|
|
94
|
+
<KyvernoPostureHeader data={data} family="imageValidating" />
|
|
95
|
+
|
|
96
|
+
{imageRefs.length > 0 && (
|
|
97
|
+
<Section title={`Image References (${imageRefs.length})`} icon={ScrollText}>
|
|
98
|
+
<div className="space-y-1">
|
|
99
|
+
{imageRefs.map((ref, i) => (
|
|
100
|
+
<div key={i} className="text-xs font-mono text-theme-text-primary break-all">
|
|
101
|
+
{ref}
|
|
102
|
+
</div>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
</Section>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{/* Attestors are the trust anchor — who must have signed. Without one,
|
|
109
|
+
image verification has nothing to check against. */}
|
|
110
|
+
{attestors.length > 0 ? (
|
|
111
|
+
<Section title={`Attestors (${attestors.length})`} icon={Fingerprint}>
|
|
112
|
+
<div className="space-y-2">
|
|
113
|
+
{attestors.map((a, i) => (
|
|
114
|
+
<div key={i} className="card-inner">
|
|
115
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
116
|
+
{a.name && <span className="text-xs font-medium text-theme-text-primary">{a.name}</span>}
|
|
117
|
+
{a.kind && <span className="badge status-neutral">{a.kind}</span>}
|
|
118
|
+
</div>
|
|
119
|
+
{a.identity && (
|
|
120
|
+
<div className="mt-1 text-xs font-mono text-theme-text-secondary break-all">{a.identity}</div>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
</Section>
|
|
126
|
+
) : (
|
|
127
|
+
<AlertBanner
|
|
128
|
+
variant="warning"
|
|
129
|
+
title="No attestors configured"
|
|
130
|
+
message="This image policy has no trusted authority to verify signatures against."
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
133
|
+
|
|
134
|
+
{attestations.length > 0 && (
|
|
135
|
+
<Section title={`Attestations (${attestations.length})`} icon={ShieldCheck}>
|
|
136
|
+
<div className="space-y-2">
|
|
137
|
+
{attestations.map((a, i) => (
|
|
138
|
+
<div key={i} className="card-inner">
|
|
139
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
140
|
+
{a.name && <span className="text-xs font-medium text-theme-text-primary">{a.name}</span>}
|
|
141
|
+
{a.kind && <span className="badge status-neutral">{a.kind}</span>}
|
|
142
|
+
</div>
|
|
143
|
+
{a.type && <div className="mt-1 text-xs font-mono text-theme-text-secondary break-all">{a.type}</div>}
|
|
144
|
+
</div>
|
|
145
|
+
))}
|
|
146
|
+
</div>
|
|
147
|
+
</Section>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
<ValidationsSection validations={getKyvernoValidations(data)} />
|
|
151
|
+
|
|
152
|
+
{extractors.length > 0 && (
|
|
153
|
+
<Section title={`Image Extractors (${extractors.length})`} icon={ScrollText} defaultExpanded={false}>
|
|
154
|
+
<KyvernoExpressionList items={extractors} />
|
|
155
|
+
</Section>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
<KyvernoMatchScopeSection data={data} />
|
|
159
|
+
<KyvernoVariablesSection data={data} />
|
|
160
|
+
<KyvernoEvaluationSection data={data} />
|
|
161
|
+
<ConditionsSection conditions={getKyvernoPolicyConditions(data)} />
|
|
162
|
+
</div>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function KyvernoMutatingPolicyRenderer({ data }: { data: any }) {
|
|
167
|
+
const mutations = getKyvernoMutations(data)
|
|
168
|
+
const reinvocation = getKyvernoReinvocationPolicy(data)
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<div className="space-y-4">
|
|
172
|
+
<KyvernoPostureHeader data={data} family="mutating" />
|
|
173
|
+
|
|
174
|
+
{mutations.length > 0 && (
|
|
175
|
+
<Section title={`Mutations (${mutations.length})`} icon={Wand2}>
|
|
176
|
+
<div className="space-y-2">
|
|
177
|
+
{mutations.map((m, i) => (
|
|
178
|
+
<div key={i} className="card-inner-lg">
|
|
179
|
+
{m.patchType && (
|
|
180
|
+
<div className="mb-1">
|
|
181
|
+
<span className="badge status-neutral">{m.patchType}</span>
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
<pre className="text-xs text-theme-text-primary font-mono whitespace-pre-wrap break-all">
|
|
185
|
+
{m.expression}
|
|
186
|
+
</pre>
|
|
187
|
+
</div>
|
|
188
|
+
))}
|
|
189
|
+
</div>
|
|
190
|
+
</Section>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
{reinvocation && (
|
|
194
|
+
<Section title="Reinvocation" icon={Wand2} defaultExpanded={false}>
|
|
195
|
+
<PropertyList>
|
|
196
|
+
<Property
|
|
197
|
+
label="Policy"
|
|
198
|
+
value={
|
|
199
|
+
<span className="flex items-center gap-2">
|
|
200
|
+
<span className="badge status-neutral">{reinvocation}</span>
|
|
201
|
+
<span className="text-xs text-theme-text-tertiary">
|
|
202
|
+
{reinvocation === 'IfNeeded'
|
|
203
|
+
? 're-runs when another webhook changes the object'
|
|
204
|
+
: 'runs once per admission request'}
|
|
205
|
+
</span>
|
|
206
|
+
</span>
|
|
207
|
+
}
|
|
208
|
+
/>
|
|
209
|
+
</PropertyList>
|
|
210
|
+
</Section>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
<KyvernoMatchScopeSection data={data} />
|
|
214
|
+
<KyvernoVariablesSection data={data} />
|
|
215
|
+
<KyvernoEvaluationSection data={data} />
|
|
216
|
+
<ConditionsSection conditions={getKyvernoPolicyConditions(data)} />
|
|
217
|
+
</div>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function KyvernoGeneratingPolicyRenderer({ data }: { data: any }) {
|
|
222
|
+
const generations = getKyvernoGenerations(data)
|
|
223
|
+
const settings = getKyvernoGenerationSettings(data)
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<div className="space-y-4">
|
|
227
|
+
<KyvernoPostureHeader data={data} family="generating" />
|
|
228
|
+
|
|
229
|
+
{generations.length > 0 && (
|
|
230
|
+
<Section title={`Generates (${generations.length})`} icon={Sparkles}>
|
|
231
|
+
<div className="space-y-2">
|
|
232
|
+
{generations.map((expr, i) => (
|
|
233
|
+
<div key={i} className="card-inner-lg">
|
|
234
|
+
<pre className="text-xs text-theme-text-primary font-mono whitespace-pre-wrap break-all">{expr}</pre>
|
|
235
|
+
</div>
|
|
236
|
+
))}
|
|
237
|
+
</div>
|
|
238
|
+
</Section>
|
|
239
|
+
)}
|
|
240
|
+
|
|
241
|
+
{/* These three decide what happens to already-generated objects, which
|
|
242
|
+
is where generation surprises operators: whether existing resources
|
|
243
|
+
are backfilled, whether edits are reverted, and whether downstream
|
|
244
|
+
objects survive deleting the policy. */}
|
|
245
|
+
<Section title="Generation Behavior" icon={Sparkles}>
|
|
246
|
+
<PropertyList>
|
|
247
|
+
<Property
|
|
248
|
+
label="Generate Existing"
|
|
249
|
+
value={settings.generateExisting ? 'Yes — applies to resources that already exist' : 'No — new resources only'}
|
|
250
|
+
/>
|
|
251
|
+
<Property
|
|
252
|
+
label="Synchronize"
|
|
253
|
+
value={
|
|
254
|
+
settings.synchronize
|
|
255
|
+
? 'Yes — manual edits to generated resources are reverted'
|
|
256
|
+
: 'No — generated resources are left alone after creation'
|
|
257
|
+
}
|
|
258
|
+
/>
|
|
259
|
+
<Property
|
|
260
|
+
label="Orphan On Delete"
|
|
261
|
+
value={
|
|
262
|
+
settings.orphanDownstreamOnPolicyDelete
|
|
263
|
+
? 'Yes — generated resources survive deleting this policy'
|
|
264
|
+
: 'No — generated resources are removed with this policy'
|
|
265
|
+
}
|
|
266
|
+
/>
|
|
267
|
+
</PropertyList>
|
|
268
|
+
</Section>
|
|
269
|
+
|
|
270
|
+
<KyvernoMatchScopeSection data={data} />
|
|
271
|
+
<KyvernoVariablesSection data={data} />
|
|
272
|
+
<ConditionsSection conditions={getKyvernoPolicyConditions(data)} />
|
|
273
|
+
</div>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function KyvernoDeletingPolicyRenderer({ data }: { data: any }) {
|
|
278
|
+
const schedule = getKyvernoSchedule(data)
|
|
279
|
+
const conditions = getKyvernoDeletionConditions(data)
|
|
280
|
+
const propagation = getKyvernoDeletionPropagationPolicy(data)
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div className="space-y-4">
|
|
284
|
+
<KyvernoPostureHeader data={data} family="deleting" />
|
|
285
|
+
|
|
286
|
+
<Section title="Schedule" icon={Clock}>
|
|
287
|
+
<PropertyList>
|
|
288
|
+
<Property
|
|
289
|
+
label="Cron"
|
|
290
|
+
value={schedule ? <span className="font-mono">{schedule}</span> : 'Not set — this policy will not run'}
|
|
291
|
+
/>
|
|
292
|
+
{propagation && <Property label="Deletion Propagation" value={propagation} />}
|
|
293
|
+
</PropertyList>
|
|
294
|
+
</Section>
|
|
295
|
+
|
|
296
|
+
{/* Without conditions every matched resource is deleted on schedule.
|
|
297
|
+
That is a materially different blast radius and has to be stated
|
|
298
|
+
rather than left as an empty section. */}
|
|
299
|
+
{conditions.length > 0 ? (
|
|
300
|
+
<Section title={`Deletion Conditions (${conditions.length})`} icon={Trash2}>
|
|
301
|
+
<KyvernoExpressionList items={conditions} />
|
|
302
|
+
</Section>
|
|
303
|
+
) : (
|
|
304
|
+
<AlertBanner
|
|
305
|
+
variant="warning"
|
|
306
|
+
icon={Trash2}
|
|
307
|
+
title="No deletion conditions"
|
|
308
|
+
message="Every resource matched by this policy is deleted when the schedule fires."
|
|
309
|
+
/>
|
|
310
|
+
)}
|
|
311
|
+
|
|
312
|
+
<KyvernoMatchScopeSection data={data} />
|
|
313
|
+
<KyvernoVariablesSection data={data} />
|
|
314
|
+
<ConditionsSection conditions={getKyvernoPolicyConditions(data)} />
|
|
315
|
+
</div>
|
|
316
|
+
)
|
|
317
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// PolicyException and CleanupPolicy renderers.
|
|
2
|
+
//
|
|
3
|
+
// PolicyException is the record of who was let off the hook — the most
|
|
4
|
+
// operationally interesting Kyverno object after the policy itself. One
|
|
5
|
+
// renderer serves both API families; the accessors it calls are
|
|
6
|
+
// group-qualified, so the same component reads whichever shape it is handed.
|
|
7
|
+
|
|
8
|
+
import { ShieldOff, Filter, Clock, Trash2, AlertTriangle, ScrollText } from 'lucide-react'
|
|
9
|
+
import {
|
|
10
|
+
Section,
|
|
11
|
+
PropertyList,
|
|
12
|
+
Property,
|
|
13
|
+
ConditionsSection,
|
|
14
|
+
AlertBanner,
|
|
15
|
+
LabelSelectorDisplay,
|
|
16
|
+
} from '../../ui/drawer-components'
|
|
17
|
+
import { KyvernoExpressionList } from './KyvernoPolicyShared'
|
|
18
|
+
import {
|
|
19
|
+
getKyvernoCleanupConditions,
|
|
20
|
+
getKyvernoCleanupExclude,
|
|
21
|
+
getKyvernoCleanupLastExecution,
|
|
22
|
+
getKyvernoCleanupMatch,
|
|
23
|
+
getKyvernoCleanupMatchedKinds,
|
|
24
|
+
getKyvernoCleanupPropagationPolicy,
|
|
25
|
+
getKyvernoCleanupSchedule,
|
|
26
|
+
getKyvernoExceptedPolicies,
|
|
27
|
+
getKyvernoExceptionLegacyConditions,
|
|
28
|
+
getKyvernoExceptionMatch,
|
|
29
|
+
getKyvernoExceptionMatchConditions,
|
|
30
|
+
getKyvernoExceptionReportResult,
|
|
31
|
+
isBlanketKyvernoException,
|
|
32
|
+
isModernKyvernoPolicyException,
|
|
33
|
+
} from '../resource-utils-kyverno-exceptions'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Renders the legacy any/all match block. Each entry carries a
|
|
37
|
+
* `resources` selector with kinds/names/namespaces/selector.
|
|
38
|
+
*/
|
|
39
|
+
function LegacyMatchBlock({ match }: { match: any }) {
|
|
40
|
+
const blocks: Array<{ operator: string; entries: any[] }> = []
|
|
41
|
+
if (Array.isArray(match?.any)) blocks.push({ operator: 'any', entries: match.any })
|
|
42
|
+
if (Array.isArray(match?.all)) blocks.push({ operator: 'all', entries: match.all })
|
|
43
|
+
if (blocks.length === 0) return null
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="space-y-2">
|
|
47
|
+
{blocks.map((block) => (
|
|
48
|
+
<div key={block.operator}>
|
|
49
|
+
<div className="text-xs uppercase tracking-wide text-theme-text-tertiary mb-1">Match {block.operator}</div>
|
|
50
|
+
<div className="space-y-2">
|
|
51
|
+
{block.entries.map((entry, i) => {
|
|
52
|
+
const resources = entry?.resources || {}
|
|
53
|
+
return (
|
|
54
|
+
<div key={i} className="card-inner">
|
|
55
|
+
<PropertyList>
|
|
56
|
+
{Array.isArray(resources.kinds) && resources.kinds.length > 0 && (
|
|
57
|
+
<Property label="Kinds" value={resources.kinds.join(', ')} />
|
|
58
|
+
)}
|
|
59
|
+
{Array.isArray(resources.names) && resources.names.length > 0 && (
|
|
60
|
+
<Property label="Names" value={resources.names.join(', ')} />
|
|
61
|
+
)}
|
|
62
|
+
{Array.isArray(resources.namespaces) && resources.namespaces.length > 0 && (
|
|
63
|
+
<Property label="Namespaces" value={resources.namespaces.join(', ')} />
|
|
64
|
+
)}
|
|
65
|
+
{resources.selector && (
|
|
66
|
+
<Property label="Selector" value={<LabelSelectorDisplay selector={resources.selector} inline />} />
|
|
67
|
+
)}
|
|
68
|
+
{resources.namespaceSelector && (
|
|
69
|
+
<Property
|
|
70
|
+
label="Namespace Selector"
|
|
71
|
+
value={<LabelSelectorDisplay selector={resources.namespaceSelector} inline />}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
</PropertyList>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
})}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function KyvernoPolicyExceptionRenderer({ data }: { data: any }) {
|
|
86
|
+
const isModern = isModernKyvernoPolicyException(data)
|
|
87
|
+
const excepted = getKyvernoExceptedPolicies(data)
|
|
88
|
+
const blanket = isBlanketKyvernoException(data)
|
|
89
|
+
const matchConditions = getKyvernoExceptionMatchConditions(data)
|
|
90
|
+
const legacyMatch = getKyvernoExceptionMatch(data)
|
|
91
|
+
const legacyConditions = getKyvernoExceptionLegacyConditions(data)
|
|
92
|
+
const reportResult = getKyvernoExceptionReportResult(data)
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="space-y-4">
|
|
96
|
+
{/* An exception naming no policy exempts everything in its match scope.
|
|
97
|
+
That is the widest exemption expressible here and the operator has to
|
|
98
|
+
see it before anything else. */}
|
|
99
|
+
{blanket && (
|
|
100
|
+
<AlertBanner
|
|
101
|
+
variant="warning"
|
|
102
|
+
icon={AlertTriangle}
|
|
103
|
+
title="Blanket exception"
|
|
104
|
+
message="This exception names no specific policy, so every policy matching its scope is bypassed."
|
|
105
|
+
/>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
<div className="card-inner-lg">
|
|
109
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
110
|
+
<span className="text-xs uppercase tracking-wide text-theme-text-tertiary">Exempts</span>
|
|
111
|
+
<span className={`badge ${blanket ? 'status-alert' : 'status-neutral'}`}>
|
|
112
|
+
{blanket ? 'All matching policies' : `${excepted.length} ${excepted.length === 1 ? 'policy' : 'policies'}`}
|
|
113
|
+
</span>
|
|
114
|
+
<span className="badge status-neutral">{isModern ? 'policies.kyverno.io' : 'kyverno.io'}</span>
|
|
115
|
+
</div>
|
|
116
|
+
{/* Kyverno has no expiry field on either shape — the upstream pattern
|
|
117
|
+
is a companion cleanup policy — so an exception persists until
|
|
118
|
+
somebody deletes it. */}
|
|
119
|
+
<div className="mt-2 text-xs text-theme-text-secondary">
|
|
120
|
+
Exceptions do not expire; this one applies until it is deleted.
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{excepted.length > 0 && (
|
|
125
|
+
<Section title={`Excepted Policies (${excepted.length})`} icon={ShieldOff}>
|
|
126
|
+
<div className="space-y-2">
|
|
127
|
+
{excepted.map((policy, i) => (
|
|
128
|
+
<div key={i} className="card-inner">
|
|
129
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
130
|
+
<span className="text-sm font-medium text-theme-text-primary">{policy.name}</span>
|
|
131
|
+
{policy.kind && <span className="badge status-neutral">{policy.kind}</span>}
|
|
132
|
+
</div>
|
|
133
|
+
{policy.ruleNames.length > 0 ? (
|
|
134
|
+
<div className="mt-1 flex items-center gap-1 flex-wrap">
|
|
135
|
+
<span className="text-xs text-theme-text-tertiary">Rules:</span>
|
|
136
|
+
{policy.ruleNames.map((rule) => (
|
|
137
|
+
<span key={rule} className="badge status-neutral">
|
|
138
|
+
{rule}
|
|
139
|
+
</span>
|
|
140
|
+
))}
|
|
141
|
+
</div>
|
|
142
|
+
) : (
|
|
143
|
+
!isModern && <div className="mt-1 text-xs text-theme-text-tertiary">All rules in this policy</div>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
))}
|
|
147
|
+
</div>
|
|
148
|
+
</Section>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
{reportResult && (
|
|
152
|
+
<Section title="Reporting" icon={ScrollText}>
|
|
153
|
+
<PropertyList>
|
|
154
|
+
<Property
|
|
155
|
+
label="Report Result"
|
|
156
|
+
value={
|
|
157
|
+
<span className="flex items-center gap-2">
|
|
158
|
+
<span className="badge status-neutral">{reportResult}</span>
|
|
159
|
+
<span className="text-xs text-theme-text-tertiary">how excepted resources appear in reports</span>
|
|
160
|
+
</span>
|
|
161
|
+
}
|
|
162
|
+
/>
|
|
163
|
+
</PropertyList>
|
|
164
|
+
</Section>
|
|
165
|
+
)}
|
|
166
|
+
|
|
167
|
+
{legacyMatch && (
|
|
168
|
+
<Section title="Match Scope" icon={Filter}>
|
|
169
|
+
<LegacyMatchBlock match={legacyMatch} />
|
|
170
|
+
</Section>
|
|
171
|
+
)}
|
|
172
|
+
|
|
173
|
+
{matchConditions.length > 0 && (
|
|
174
|
+
<Section title={`Match Conditions (${matchConditions.length})`} icon={Filter}>
|
|
175
|
+
<KyvernoExpressionList items={matchConditions} />
|
|
176
|
+
</Section>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{legacyConditions && (
|
|
180
|
+
<Section title="Conditions" icon={Filter} defaultExpanded={false}>
|
|
181
|
+
<pre className="text-xs text-theme-text-secondary font-mono whitespace-pre-wrap break-all">
|
|
182
|
+
{JSON.stringify(legacyConditions, null, 2)}
|
|
183
|
+
</pre>
|
|
184
|
+
</Section>
|
|
185
|
+
)}
|
|
186
|
+
|
|
187
|
+
<ConditionsSection conditions={data?.status?.conditions} />
|
|
188
|
+
</div>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* CleanupPolicy / ClusterCleanupPolicy (kyverno.io/v2), deprecated in Kyverno
|
|
194
|
+
* 1.18 in favour of DeletingPolicy. Rendered because the installed base runs
|
|
195
|
+
* it today and "what deleted my resource?" is an expensive question to answer
|
|
196
|
+
* without it.
|
|
197
|
+
*/
|
|
198
|
+
export function KyvernoCleanupPolicyRenderer({ data }: { data: any }) {
|
|
199
|
+
const schedule = getKyvernoCleanupSchedule(data)
|
|
200
|
+
const kinds = getKyvernoCleanupMatchedKinds(data)
|
|
201
|
+
const lastExecution = getKyvernoCleanupLastExecution(data)
|
|
202
|
+
const propagation = getKyvernoCleanupPropagationPolicy(data)
|
|
203
|
+
const match = getKyvernoCleanupMatch(data)
|
|
204
|
+
const exclude = getKyvernoCleanupExclude(data)
|
|
205
|
+
const conditions = getKyvernoCleanupConditions(data)
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="space-y-4">
|
|
209
|
+
<div className="card-inner-lg">
|
|
210
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
211
|
+
<span className="text-xs uppercase tracking-wide text-theme-text-tertiary">Deletes</span>
|
|
212
|
+
<span className="badge status-alert">{kinds.length > 0 ? kinds.join(', ') : 'matched resources'}</span>
|
|
213
|
+
{schedule ? (
|
|
214
|
+
<span className="text-xs text-theme-text-secondary">
|
|
215
|
+
on schedule <span className="font-mono">{schedule}</span>
|
|
216
|
+
</span>
|
|
217
|
+
) : (
|
|
218
|
+
<span className="text-xs text-theme-text-secondary">no schedule set — this policy will not run</span>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
<div className="mt-2 text-xs text-theme-text-secondary">
|
|
222
|
+
Deprecated in Kyverno 1.18; DeletingPolicy (policies.kyverno.io) is its replacement.
|
|
223
|
+
</div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<Section title="Schedule" icon={Clock}>
|
|
227
|
+
<PropertyList>
|
|
228
|
+
<Property label="Cron" value={schedule ? <span className="font-mono">{schedule}</span> : 'Not set'} />
|
|
229
|
+
{propagation && <Property label="Deletion Propagation" value={propagation} />}
|
|
230
|
+
{lastExecution && <Property label="Last Execution" value={lastExecution} />}
|
|
231
|
+
</PropertyList>
|
|
232
|
+
</Section>
|
|
233
|
+
|
|
234
|
+
{match && (
|
|
235
|
+
<Section title="Match Scope" icon={Filter}>
|
|
236
|
+
<LegacyMatchBlock match={match} />
|
|
237
|
+
</Section>
|
|
238
|
+
)}
|
|
239
|
+
|
|
240
|
+
{exclude && (
|
|
241
|
+
<Section title="Excluded" icon={Filter} defaultExpanded={false}>
|
|
242
|
+
<LegacyMatchBlock match={exclude} />
|
|
243
|
+
</Section>
|
|
244
|
+
)}
|
|
245
|
+
|
|
246
|
+
{conditions ? (
|
|
247
|
+
<Section title="Deletion Conditions" icon={Trash2}>
|
|
248
|
+
<pre className="text-xs text-theme-text-secondary font-mono whitespace-pre-wrap break-all">
|
|
249
|
+
{JSON.stringify(conditions, null, 2)}
|
|
250
|
+
</pre>
|
|
251
|
+
</Section>
|
|
252
|
+
) : (
|
|
253
|
+
<AlertBanner
|
|
254
|
+
variant="warning"
|
|
255
|
+
icon={Trash2}
|
|
256
|
+
title="No deletion conditions"
|
|
257
|
+
message="Every resource matched by this policy is deleted when the schedule fires."
|
|
258
|
+
/>
|
|
259
|
+
)}
|
|
260
|
+
|
|
261
|
+
<ConditionsSection conditions={data?.status?.conditions} />
|
|
262
|
+
</div>
|
|
263
|
+
)
|
|
264
|
+
}
|