@skyhook-io/k8s-ui 1.12.0 → 1.13.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 +4 -4
- 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 +410 -5
- 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/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 +12 -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/ScheduleValue.test.tsx +80 -0
- package/src/components/ui/ScheduleValue.tsx +85 -0
- 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 +3 -0
- package/src/types/core.ts +29 -0
- package/src/types/policy.ts +153 -2
- 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
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { useId } from 'react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { Badge, type BadgeSeverity } from '../../ui/Badge'
|
|
4
|
+
import { Tooltip } from '../../ui/Tooltip'
|
|
5
|
+
import {
|
|
6
|
+
formatKubernetesLabelSelector,
|
|
7
|
+
getCalicoPolicyTypes,
|
|
8
|
+
} from '../resource-utils-calico'
|
|
9
|
+
import {
|
|
10
|
+
NETWORK_POLICY_PEER_DOTS,
|
|
11
|
+
NETWORK_POLICY_PEER_STYLES,
|
|
12
|
+
type NetworkPolicyPeerType,
|
|
13
|
+
} from './network-policy-peer-styles'
|
|
14
|
+
|
|
15
|
+
interface CalicoNetworkPolicyDiagramProps {
|
|
16
|
+
spec: any
|
|
17
|
+
staged?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Direction = 'ingress' | 'egress'
|
|
21
|
+
|
|
22
|
+
interface EntityField {
|
|
23
|
+
label: string
|
|
24
|
+
values: string[]
|
|
25
|
+
negative?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ACTION_SEVERITY: Record<string, BadgeSeverity> = {
|
|
29
|
+
allow: 'success',
|
|
30
|
+
deny: 'error',
|
|
31
|
+
log: 'warning',
|
|
32
|
+
pass: 'info',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function CalicoNetworkPolicyDiagram({
|
|
36
|
+
spec,
|
|
37
|
+
staged = false,
|
|
38
|
+
}: CalicoNetworkPolicyDiagramProps) {
|
|
39
|
+
const policyTypes = getCalicoPolicyTypes({ spec }).map((type) =>
|
|
40
|
+
type.toLowerCase(),
|
|
41
|
+
)
|
|
42
|
+
const directions = [
|
|
43
|
+
{
|
|
44
|
+
direction: 'ingress' as const,
|
|
45
|
+
label: 'Ingress',
|
|
46
|
+
enabled:
|
|
47
|
+
policyTypes.length > 0
|
|
48
|
+
? policyTypes.includes('ingress')
|
|
49
|
+
: Array.isArray(spec?.ingress),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
direction: 'egress' as const,
|
|
53
|
+
label: 'Egress',
|
|
54
|
+
enabled:
|
|
55
|
+
policyTypes.length > 0
|
|
56
|
+
? policyTypes.includes('egress')
|
|
57
|
+
: Array.isArray(spec?.egress),
|
|
58
|
+
},
|
|
59
|
+
].filter((entry) => entry.enabled)
|
|
60
|
+
const target = {
|
|
61
|
+
selector: spec?.selector,
|
|
62
|
+
namespaceSelector: spec?.namespaceSelector,
|
|
63
|
+
serviceAccountSelector: spec?.serviceAccountSelector,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div className="card-inner-lg space-y-3">
|
|
68
|
+
{staged && (
|
|
69
|
+
<div className="flex items-center gap-2 text-[11px] text-theme-text-tertiary">
|
|
70
|
+
<Badge severity="warning" size="sm">
|
|
71
|
+
Staged preview
|
|
72
|
+
</Badge>
|
|
73
|
+
<span>Dashed paths are evaluated but not enforced</span>
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
|
|
77
|
+
{directions.map(({ direction, label }) => (
|
|
78
|
+
<DirectionSection
|
|
79
|
+
key={direction}
|
|
80
|
+
direction={direction}
|
|
81
|
+
label={label}
|
|
82
|
+
rules={Array.isArray(spec?.[direction]) ? spec[direction] : []}
|
|
83
|
+
target={target}
|
|
84
|
+
staged={staged}
|
|
85
|
+
/>
|
|
86
|
+
))}
|
|
87
|
+
|
|
88
|
+
{directions.length === 0 && (
|
|
89
|
+
<div className="py-2 text-center text-xs text-theme-text-tertiary">
|
|
90
|
+
No explicit ingress or egress rules
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function DirectionSection({
|
|
98
|
+
direction,
|
|
99
|
+
label,
|
|
100
|
+
rules,
|
|
101
|
+
target,
|
|
102
|
+
staged,
|
|
103
|
+
}: {
|
|
104
|
+
direction: Direction
|
|
105
|
+
label: string
|
|
106
|
+
rules: any[]
|
|
107
|
+
target: any
|
|
108
|
+
staged: boolean
|
|
109
|
+
}) {
|
|
110
|
+
return (
|
|
111
|
+
<div className="space-y-2">
|
|
112
|
+
<div className="flex items-center gap-1.5">
|
|
113
|
+
<span
|
|
114
|
+
className={clsx(
|
|
115
|
+
'h-1.5 w-1.5 rounded-full',
|
|
116
|
+
direction === 'ingress' ? 'bg-blue-500' : 'bg-purple-500',
|
|
117
|
+
)}
|
|
118
|
+
/>
|
|
119
|
+
<span
|
|
120
|
+
className={clsx(
|
|
121
|
+
'text-[10px] font-semibold uppercase tracking-wider',
|
|
122
|
+
direction === 'ingress' ? 'text-blue-400' : 'text-purple-400',
|
|
123
|
+
)}
|
|
124
|
+
>
|
|
125
|
+
{label}
|
|
126
|
+
</span>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
{rules.length === 0 ? (
|
|
130
|
+
<div className="rounded-md border border-theme-border-subtle px-2 py-2 text-xs text-theme-text-tertiary">
|
|
131
|
+
No explicit {direction} rules
|
|
132
|
+
</div>
|
|
133
|
+
) : (
|
|
134
|
+
<div className="divide-y divide-theme-border-subtle rounded-md border border-theme-border-subtle bg-theme-base/40">
|
|
135
|
+
{rules.map((rule, index) => (
|
|
136
|
+
<FlowRow
|
|
137
|
+
key={index}
|
|
138
|
+
direction={direction}
|
|
139
|
+
rule={rule}
|
|
140
|
+
target={target}
|
|
141
|
+
staged={staged}
|
|
142
|
+
/>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function FlowRow({
|
|
151
|
+
direction,
|
|
152
|
+
rule,
|
|
153
|
+
target,
|
|
154
|
+
staged,
|
|
155
|
+
}: {
|
|
156
|
+
direction: Direction
|
|
157
|
+
rule: any
|
|
158
|
+
target: any
|
|
159
|
+
staged: boolean
|
|
160
|
+
}) {
|
|
161
|
+
const action = String(rule?.action ?? 'Allow')
|
|
162
|
+
const protocol = rule?.protocol
|
|
163
|
+
const notProtocol = rule?.notProtocol
|
|
164
|
+
const isIngress = direction === 'ingress'
|
|
165
|
+
const constraints = pathConstraints(rule)
|
|
166
|
+
const flowConstraints =
|
|
167
|
+
constraints.length > 0
|
|
168
|
+
? constraints
|
|
169
|
+
: protocol !== undefined
|
|
170
|
+
? [`${formatProtocol(protocol).toUpperCase()}/*`]
|
|
171
|
+
: []
|
|
172
|
+
const leftEntities = isIngress ? [rule?.source] : [target, rule?.source]
|
|
173
|
+
const rightEntities = isIngress
|
|
174
|
+
? [target, rule?.destination]
|
|
175
|
+
: [rule?.destination]
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div className="space-y-2 px-2 py-2">
|
|
179
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
180
|
+
<Badge
|
|
181
|
+
severity={ACTION_SEVERITY[action.toLowerCase()] ?? 'neutral'}
|
|
182
|
+
size="sm"
|
|
183
|
+
>
|
|
184
|
+
{action}
|
|
185
|
+
</Badge>
|
|
186
|
+
{notProtocol !== undefined && (
|
|
187
|
+
<Badge tone="note" size="sm">
|
|
188
|
+
not {formatProtocol(notProtocol)}
|
|
189
|
+
</Badge>
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
<div className="grid grid-cols-[minmax(0,1fr)_3.5rem_minmax(0,1fr)] items-center gap-1">
|
|
194
|
+
<Endpoint
|
|
195
|
+
entities={leftEntities}
|
|
196
|
+
emptyText={isIngress ? 'Any source' : 'All workloads'}
|
|
197
|
+
target={!isIngress}
|
|
198
|
+
/>
|
|
199
|
+
<FlowArrow
|
|
200
|
+
action={action}
|
|
201
|
+
direction={direction}
|
|
202
|
+
staged={staged}
|
|
203
|
+
constraints={flowConstraints}
|
|
204
|
+
/>
|
|
205
|
+
<Endpoint
|
|
206
|
+
entities={rightEntities}
|
|
207
|
+
emptyText={isIngress ? 'All workloads' : 'Any destination'}
|
|
208
|
+
target={isIngress}
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function FlowArrow({
|
|
216
|
+
action,
|
|
217
|
+
direction,
|
|
218
|
+
staged,
|
|
219
|
+
constraints,
|
|
220
|
+
}: {
|
|
221
|
+
action: string
|
|
222
|
+
direction: Direction
|
|
223
|
+
staged: boolean
|
|
224
|
+
constraints: string[]
|
|
225
|
+
}) {
|
|
226
|
+
const markerId = useId().replace(/:/g, '')
|
|
227
|
+
const color = arrowColor(action, direction)
|
|
228
|
+
const dashed = staged || action.toLowerCase() !== 'allow'
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div className="flex min-w-0 flex-col items-center justify-center">
|
|
232
|
+
<svg
|
|
233
|
+
width="48"
|
|
234
|
+
height="18"
|
|
235
|
+
viewBox="0 0 48 18"
|
|
236
|
+
className="overflow-visible"
|
|
237
|
+
aria-hidden="true"
|
|
238
|
+
>
|
|
239
|
+
<defs>
|
|
240
|
+
<marker
|
|
241
|
+
id={markerId}
|
|
242
|
+
markerWidth="6"
|
|
243
|
+
markerHeight="6"
|
|
244
|
+
refX="5"
|
|
245
|
+
refY="3"
|
|
246
|
+
orient="auto"
|
|
247
|
+
>
|
|
248
|
+
<path
|
|
249
|
+
d="M0,0 L6,3 L0,6"
|
|
250
|
+
fill="none"
|
|
251
|
+
stroke={color}
|
|
252
|
+
strokeWidth="1.5"
|
|
253
|
+
/>
|
|
254
|
+
</marker>
|
|
255
|
+
</defs>
|
|
256
|
+
<line
|
|
257
|
+
x1="2"
|
|
258
|
+
y1="9"
|
|
259
|
+
x2="40"
|
|
260
|
+
y2="9"
|
|
261
|
+
stroke={color}
|
|
262
|
+
strokeWidth="1.5"
|
|
263
|
+
strokeDasharray={dashed ? '4 3' : undefined}
|
|
264
|
+
markerEnd={`url(#${markerId})`}
|
|
265
|
+
/>
|
|
266
|
+
</svg>
|
|
267
|
+
{constraints.length > 0 && (
|
|
268
|
+
<Tooltip content={constraints.join('\n')} position="top">
|
|
269
|
+
<span className="flex flex-col items-center gap-0.5 font-mono text-[8px] leading-none text-theme-text-tertiary">
|
|
270
|
+
{constraints.map((constraint, index) => (
|
|
271
|
+
<span
|
|
272
|
+
key={`${constraint}-${index}`}
|
|
273
|
+
className="whitespace-nowrap"
|
|
274
|
+
>
|
|
275
|
+
{constraint}
|
|
276
|
+
</span>
|
|
277
|
+
))}
|
|
278
|
+
</span>
|
|
279
|
+
</Tooltip>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function Endpoint({
|
|
286
|
+
entities,
|
|
287
|
+
emptyText,
|
|
288
|
+
target = false,
|
|
289
|
+
}: {
|
|
290
|
+
entities: any[]
|
|
291
|
+
emptyText: string
|
|
292
|
+
target?: boolean
|
|
293
|
+
}) {
|
|
294
|
+
const fields = entities.flatMap(entityFields)
|
|
295
|
+
const peerType = classifyPeer(fields)
|
|
296
|
+
const tooltip = fields.length
|
|
297
|
+
? fields
|
|
298
|
+
.map((field) => `${field.label}: ${field.values.join(', ')}`)
|
|
299
|
+
.join('\n')
|
|
300
|
+
: emptyText
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<Tooltip content={tooltip} position="top">
|
|
304
|
+
<div
|
|
305
|
+
className={clsx(
|
|
306
|
+
'min-w-0 overflow-hidden rounded-md border px-2 py-1.5',
|
|
307
|
+
target
|
|
308
|
+
? 'border-indigo-500/30 bg-indigo-500/8'
|
|
309
|
+
: NETWORK_POLICY_PEER_STYLES[peerType],
|
|
310
|
+
)}
|
|
311
|
+
>
|
|
312
|
+
<div className="flex min-w-0 items-start gap-1.5">
|
|
313
|
+
<span
|
|
314
|
+
className={clsx(
|
|
315
|
+
'mt-1 h-1.5 w-1.5 shrink-0 rounded-full',
|
|
316
|
+
target ? 'bg-indigo-500' : NETWORK_POLICY_PEER_DOTS[peerType],
|
|
317
|
+
)}
|
|
318
|
+
/>
|
|
319
|
+
<div className="min-w-0 flex-1">
|
|
320
|
+
{fields.length > 0 ? (
|
|
321
|
+
<EntityFields fields={fields} />
|
|
322
|
+
) : (
|
|
323
|
+
<div className="truncate text-[11px] font-medium">
|
|
324
|
+
{emptyText}
|
|
325
|
+
</div>
|
|
326
|
+
)}
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
</Tooltip>
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function EntityFields({ fields }: { fields: EntityField[] }) {
|
|
335
|
+
return (
|
|
336
|
+
<div className="min-w-0 space-y-0.5">
|
|
337
|
+
{fields.map((field, index) => (
|
|
338
|
+
<div
|
|
339
|
+
key={`${field.label}-${index}`}
|
|
340
|
+
className="flex min-w-0 items-baseline gap-1 text-[9px]"
|
|
341
|
+
>
|
|
342
|
+
<span
|
|
343
|
+
className={clsx(
|
|
344
|
+
'shrink-0 text-theme-text-tertiary',
|
|
345
|
+
field.negative && 'text-warning-text',
|
|
346
|
+
)}
|
|
347
|
+
>
|
|
348
|
+
{field.label}
|
|
349
|
+
</span>
|
|
350
|
+
<span
|
|
351
|
+
className={clsx(
|
|
352
|
+
'min-w-0 truncate font-mono text-[10px] text-theme-text-primary',
|
|
353
|
+
field.negative && 'line-through',
|
|
354
|
+
)}
|
|
355
|
+
>
|
|
356
|
+
{field.values.join(', ')}
|
|
357
|
+
</span>
|
|
358
|
+
</div>
|
|
359
|
+
))}
|
|
360
|
+
</div>
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function classifyPeer(fields: EntityField[]): NetworkPolicyPeerType {
|
|
365
|
+
const labels = new Set(fields.map((field) => field.label))
|
|
366
|
+
const hasPodMatch = [
|
|
367
|
+
'Selector',
|
|
368
|
+
'Pod selector',
|
|
369
|
+
'Not selector',
|
|
370
|
+
'Service account',
|
|
371
|
+
'Service accounts',
|
|
372
|
+
'Not service accounts',
|
|
373
|
+
].some((label) => labels.has(label))
|
|
374
|
+
const hasNamespaceMatch = ['Namespace', 'Not namespace'].some((label) =>
|
|
375
|
+
labels.has(label),
|
|
376
|
+
)
|
|
377
|
+
const hasNetworkMatch = ['Nets', 'Not Nets', 'IP block'].some((label) =>
|
|
378
|
+
labels.has(label),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if (hasPodMatch && hasNamespaceMatch) return 'combined'
|
|
382
|
+
if (hasPodMatch) return 'pod'
|
|
383
|
+
if (hasNamespaceMatch) return 'namespace'
|
|
384
|
+
if (hasNetworkMatch) return 'cidr'
|
|
385
|
+
return 'all'
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function entityFields(entity: any): EntityField[] {
|
|
389
|
+
if (!entity || typeof entity !== 'object') return []
|
|
390
|
+
|
|
391
|
+
const fields: EntityField[] = []
|
|
392
|
+
addField(fields, 'Selector', entity.selector)
|
|
393
|
+
addField(
|
|
394
|
+
fields,
|
|
395
|
+
'Pod selector',
|
|
396
|
+
entity.podSelector === undefined
|
|
397
|
+
? undefined
|
|
398
|
+
: formatKubernetesLabelSelector(entity.podSelector),
|
|
399
|
+
)
|
|
400
|
+
addField(fields, 'Not selector', entity.notSelector, true)
|
|
401
|
+
addField(
|
|
402
|
+
fields,
|
|
403
|
+
'Namespace',
|
|
404
|
+
entity.namespaceSelector && typeof entity.namespaceSelector === 'object'
|
|
405
|
+
? formatKubernetesLabelSelector(entity.namespaceSelector)
|
|
406
|
+
: entity.namespaceSelector,
|
|
407
|
+
)
|
|
408
|
+
addField(fields, 'Not namespace', entity.notNamespaceSelector, true)
|
|
409
|
+
addField(fields, 'Service account', entity.serviceAccountSelector)
|
|
410
|
+
addField(fields, 'Nets', entity.nets)
|
|
411
|
+
addField(fields, 'IP block', entity.ipBlock)
|
|
412
|
+
addField(fields, 'Not Nets', entity.notNets, true)
|
|
413
|
+
addField(
|
|
414
|
+
fields,
|
|
415
|
+
'Service accounts',
|
|
416
|
+
serviceAccountValues(entity.serviceAccounts),
|
|
417
|
+
)
|
|
418
|
+
addField(
|
|
419
|
+
fields,
|
|
420
|
+
'Not service accounts',
|
|
421
|
+
serviceAccountValues(entity.notServiceAccounts),
|
|
422
|
+
true,
|
|
423
|
+
)
|
|
424
|
+
return fields
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function addField(
|
|
428
|
+
fields: EntityField[],
|
|
429
|
+
label: string,
|
|
430
|
+
value: any,
|
|
431
|
+
negative = false,
|
|
432
|
+
) {
|
|
433
|
+
const values = valuesFor(value)
|
|
434
|
+
.map(formatValue)
|
|
435
|
+
.filter((item) => item !== '')
|
|
436
|
+
if (values.length === 0) return
|
|
437
|
+
fields.push({ label, values, negative })
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function valuesFor(value: any): any[] {
|
|
441
|
+
if (value === undefined || value === null || value === '') return []
|
|
442
|
+
return Array.isArray(value) ? value : [value]
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function serviceAccountValues(value: any): string[] {
|
|
446
|
+
if (!value || typeof value !== 'object') return []
|
|
447
|
+
return [
|
|
448
|
+
...(Array.isArray(value.names) ? value.names.map(String) : []),
|
|
449
|
+
...(value.selector !== undefined
|
|
450
|
+
? [`selector: ${String(value.selector)}`]
|
|
451
|
+
: []),
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function pathConstraints(rule: any): string[] {
|
|
456
|
+
const sourcePorts = valuesFor(rule?.source?.ports).map((port) =>
|
|
457
|
+
formatPort(port, rule?.protocol),
|
|
458
|
+
)
|
|
459
|
+
const notSourcePorts = valuesFor(rule?.source?.notPorts).map((port) =>
|
|
460
|
+
formatPort(port, rule?.protocol),
|
|
461
|
+
)
|
|
462
|
+
const destinationPorts = valuesFor(rule?.destination?.ports).map((port) =>
|
|
463
|
+
formatPort(port, rule?.protocol),
|
|
464
|
+
)
|
|
465
|
+
const notDestinationPorts = valuesFor(rule?.destination?.notPorts).map(
|
|
466
|
+
(port) => formatPort(port, rule?.protocol),
|
|
467
|
+
)
|
|
468
|
+
return [
|
|
469
|
+
...sourcePorts.map((port) => `src:${port}`),
|
|
470
|
+
...notSourcePorts.map((port) => `src:!${port}`),
|
|
471
|
+
...destinationPorts,
|
|
472
|
+
...notDestinationPorts.map((port) => `!${port}`),
|
|
473
|
+
]
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function arrowColor(action: string, direction: Direction): string {
|
|
477
|
+
switch (action.toLowerCase()) {
|
|
478
|
+
case 'deny':
|
|
479
|
+
return '#ef4444'
|
|
480
|
+
case 'log':
|
|
481
|
+
return '#f59e0b'
|
|
482
|
+
case 'pass':
|
|
483
|
+
return '#3b82f6'
|
|
484
|
+
default:
|
|
485
|
+
return direction === 'ingress' ? '#3b82f6' : '#a855f7'
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function formatValue(value: any): string {
|
|
490
|
+
if (value && typeof value === 'object') {
|
|
491
|
+
if ('port' in value) {
|
|
492
|
+
const protocol = value.protocol ? `${value.protocol}/` : ''
|
|
493
|
+
const endPort = value.endPort !== undefined ? `-${value.endPort}` : ''
|
|
494
|
+
return `${protocol}${value.port}${endPort}`
|
|
495
|
+
}
|
|
496
|
+
if ('start' in value || 'end' in value) {
|
|
497
|
+
return `${value.start ?? ''}-${value.end ?? ''}`
|
|
498
|
+
}
|
|
499
|
+
if ('strVal' in value) return String(value.strVal)
|
|
500
|
+
if ('intVal' in value) return String(value.intVal)
|
|
501
|
+
return JSON.stringify(value)
|
|
502
|
+
}
|
|
503
|
+
return String(value)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function formatPort(value: any, ruleProtocol?: any): string {
|
|
507
|
+
if (value && typeof value === 'object') {
|
|
508
|
+
const protocol = value.protocol
|
|
509
|
+
? formatProtocol(value.protocol).toUpperCase()
|
|
510
|
+
: ruleProtocol !== undefined
|
|
511
|
+
? formatProtocol(ruleProtocol).toUpperCase()
|
|
512
|
+
: 'TCP'
|
|
513
|
+
if ('port' in value) {
|
|
514
|
+
const endPort = value.endPort !== undefined ? `-${value.endPort}` : ''
|
|
515
|
+
return `${protocol}/${value.port}${endPort}`
|
|
516
|
+
}
|
|
517
|
+
if ('start' in value || 'end' in value) {
|
|
518
|
+
return `${protocol}/${value.start ?? ''}-${value.end ?? ''}`
|
|
519
|
+
}
|
|
520
|
+
if ('strVal' in value) return `${protocol}/${value.strVal}`
|
|
521
|
+
if ('intVal' in value) return `${protocol}/${value.intVal}`
|
|
522
|
+
}
|
|
523
|
+
const protocol =
|
|
524
|
+
ruleProtocol !== undefined
|
|
525
|
+
? formatProtocol(ruleProtocol).toUpperCase()
|
|
526
|
+
: 'TCP'
|
|
527
|
+
return `${protocol}/${formatValue(value)}`
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function formatProtocol(value: any): string {
|
|
531
|
+
if (value && typeof value === 'object') {
|
|
532
|
+
if (value.strVal !== undefined) return String(value.strVal)
|
|
533
|
+
if (value.intVal !== undefined) return String(value.intVal)
|
|
534
|
+
return JSON.stringify(value)
|
|
535
|
+
}
|
|
536
|
+
return String(value)
|
|
537
|
+
}
|