@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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import { LookupFailureNote } from './LookupFailureNote'
|
|
4
|
+
|
|
5
|
+
function err(message: string, status?: number): Error {
|
|
6
|
+
return Object.assign(new Error(message), status === undefined ? {} : { status })
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('LookupFailureNote', () => {
|
|
10
|
+
it('says nothing when every lookup came back', () => {
|
|
11
|
+
expect(renderToString(<LookupFailureNote errors={[]} what="x" />)).toBe('')
|
|
12
|
+
expect(renderToString(<LookupFailureNote errors={[null, undefined]} what="x" />)).toBe('')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// Being unable to see something is expected and not actionable; a fault is
|
|
16
|
+
// neither. The two must not read alike.
|
|
17
|
+
it('renders a permission answer as a calm note, not a red error', () => {
|
|
18
|
+
const html = renderToString(
|
|
19
|
+
<LookupFailureNote errors={[err('forbidden', 403)]} what="which clusters use this store" />,
|
|
20
|
+
)
|
|
21
|
+
expect(html).toContain('permission')
|
|
22
|
+
expect(html).not.toContain('text-red-400')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('keeps a genuine fault loud, with the reason', () => {
|
|
26
|
+
const html = renderToString(
|
|
27
|
+
<LookupFailureNote errors={[err('Failed to fetch')]} what="what replicates here" />,
|
|
28
|
+
)
|
|
29
|
+
expect(html).toContain('text-red-400')
|
|
30
|
+
expect(html).toContain('Failed to fetch')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// A fault is actionable and a permission answer is not, so the actionable one
|
|
34
|
+
// is the one worth showing when both happened.
|
|
35
|
+
it('prefers the fault when a lookup was both forbidden and broken', () => {
|
|
36
|
+
const html = renderToString(
|
|
37
|
+
<LookupFailureNote errors={[err('forbidden', 403), err('boom', 500)]} what="x" />,
|
|
38
|
+
)
|
|
39
|
+
expect(html).toContain('boom')
|
|
40
|
+
expect(html).toContain('text-red-400')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
// The case that loses counts: rows are on screen, so the failure means the
|
|
44
|
+
// list is short — not that nothing was found.
|
|
45
|
+
it('says the list is incomplete rather than unchecked when rows are showing', () => {
|
|
46
|
+
const html = renderToString(
|
|
47
|
+
<LookupFailureNote errors={[err('boom', 500)]} what="x" incomplete />,
|
|
48
|
+
)
|
|
49
|
+
expect(html).toContain('This may be incomplete')
|
|
50
|
+
expect(html).toContain('boom')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('does the same for a partial permission answer', () => {
|
|
54
|
+
const html = renderToString(
|
|
55
|
+
<LookupFailureNote errors={[err('forbidden', 403)]} what="x" incomplete />,
|
|
56
|
+
)
|
|
57
|
+
expect(html).toContain('may be incomplete')
|
|
58
|
+
expect(html).not.toContain('text-red-400')
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isForbiddenError } from '../../../types/fetch-error'
|
|
2
|
+
|
|
3
|
+
interface LookupFailureNoteProps {
|
|
4
|
+
/** Nulls are ignored, so callers can pass `[a.error, b.error]` from their hooks. */
|
|
5
|
+
errors: unknown[]
|
|
6
|
+
/** Tail of a sentence: "which clusters use this store". */
|
|
7
|
+
what: string
|
|
8
|
+
/** Rows are already on screen, so the failure shortened the list rather than
|
|
9
|
+
* emptying it. */
|
|
10
|
+
incomplete?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A reverse lookup that did not come back, told apart from one that found
|
|
15
|
+
* nothing. Same split as RBACErrorSection and sharing its predicate: a denial is
|
|
16
|
+
* expected and reads calm, a fault stays loud.
|
|
17
|
+
*/
|
|
18
|
+
export function LookupFailureNote({ errors, what, incomplete }: LookupFailureNoteProps) {
|
|
19
|
+
const real = errors.filter(Boolean)
|
|
20
|
+
if (real.length === 0) return null
|
|
21
|
+
|
|
22
|
+
// A fault outranks a denial: only one of the two is actionable.
|
|
23
|
+
const fault = real.find((e) => !isForbiddenError(e))
|
|
24
|
+
if (!fault) {
|
|
25
|
+
return (
|
|
26
|
+
<div className="text-xs text-theme-text-tertiary">
|
|
27
|
+
{incomplete
|
|
28
|
+
? `This may be incomplete: you don’t have permission to check ${what}.`
|
|
29
|
+
: `You don’t have permission to check ${what}.`}
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const message = fault instanceof Error ? fault.message : String(fault)
|
|
35
|
+
return (
|
|
36
|
+
<div className="text-xs text-red-400">
|
|
37
|
+
{incomplete
|
|
38
|
+
? `This may be incomplete — could not check ${what}: ${message}`
|
|
39
|
+
: `Could not check ${what}: ${message}`}
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { quotaUsageRatio } from './NamespaceRenderer'
|
|
3
|
+
|
|
4
|
+
describe('quotaUsageRatio', () => {
|
|
5
|
+
// status.used is computed with quantity arithmetic, so a used pod count of
|
|
6
|
+
// 1000 serializes canonically as "1k". A raw-float read sees 1 and reports a
|
|
7
|
+
// saturated quota as barely used.
|
|
8
|
+
it('reads count quantities with SI suffixes on either side of the ratio', () => {
|
|
9
|
+
expect(quotaUsageRatio('pods', '1k', '1k')).toBe(1)
|
|
10
|
+
expect(quotaUsageRatio('pods', '900', '1k')).toBeCloseTo(0.9)
|
|
11
|
+
expect(quotaUsageRatio('count/pods', '1k', '2k')).toBeCloseTo(0.5)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('leaves plain counts unchanged', () => {
|
|
15
|
+
expect(quotaUsageRatio('pods', '5', '10')).toBe(0.5)
|
|
16
|
+
expect(quotaUsageRatio('services', '0', '10')).toBe(0)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('parses cpu and memory with their own unit parsers', () => {
|
|
20
|
+
expect(quotaUsageRatio('cpu', '500m', '1')).toBeCloseTo(0.5)
|
|
21
|
+
expect(quotaUsageRatio('requests.cpu', '2', '4')).toBeCloseTo(0.5)
|
|
22
|
+
expect(quotaUsageRatio('memory', '512Mi', '1Gi')).toBeCloseTo(0.5)
|
|
23
|
+
expect(quotaUsageRatio('requests.storage', '1Gi', '2Gi')).toBeCloseTo(0.5)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('yields null when hard is unset or unparseable so the row falls back to raw strings', () => {
|
|
27
|
+
expect(quotaUsageRatio('pods', '5', '')).toBeNull()
|
|
28
|
+
expect(quotaUsageRatio('pods', '5', 'garbage')).toBeNull()
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -5,7 +5,7 @@ import type { RBACNamespaceResponse, RBACBindingWithSubjects, RBACSubject, Resou
|
|
|
5
5
|
import { rbacKindBadgeClass } from '../../../utils/rbac-badges'
|
|
6
6
|
import { RBACErrorSection } from './RBACErrorSection'
|
|
7
7
|
import { SEVERITY_TEXT, SEVERITY_DOT } from '../../../utils/badge-colors'
|
|
8
|
-
import { parseCPUToNanocores, parseMemoryToBytes } from '../../../utils/format'
|
|
8
|
+
import { parseCPUToNanocores, parseMemoryToBytes, parseQuantityToNumber } from '../../../utils/format'
|
|
9
9
|
|
|
10
10
|
interface NamespaceRendererProps {
|
|
11
11
|
data: any
|
|
@@ -95,11 +95,15 @@ export function NamespaceRenderer({ data, rbacData, rbacLoading, rbacError, quot
|
|
|
95
95
|
// right unit parser by resource name (cpu → millicores, memory/storage →
|
|
96
96
|
// bytes, everything else → plain count). Returns null when hard is unset or
|
|
97
97
|
// unparseable so the row falls back to showing the raw strings.
|
|
98
|
-
|
|
98
|
+
// Count values are Quantities too: the quota controller computes status.used
|
|
99
|
+
// with quantity arithmetic, which re-serializes canonically — a used pod count
|
|
100
|
+
// of 1000 arrives as "1k". Read with parseFloat that is 1, and a saturated
|
|
101
|
+
// quota renders as barely used.
|
|
102
|
+
export function quotaUsageRatio(resourceName: string, used: string, hard: string): number | null {
|
|
99
103
|
if (!hard) return null
|
|
100
104
|
const isCPU = /(^|\.)cpu$/i.test(resourceName)
|
|
101
105
|
const isBytes = /(memory|storage)$/i.test(resourceName)
|
|
102
|
-
const parse = isCPU ? parseCPUToNanocores : isBytes ? parseMemoryToBytes :
|
|
106
|
+
const parse = isCPU ? parseCPUToNanocores : isBytes ? parseMemoryToBytes : parseQuantityToNumber
|
|
103
107
|
const h = parse(hard)
|
|
104
108
|
if (!h) return null
|
|
105
109
|
return parse(used || '0') / h
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { clsx } from 'clsx'
|
|
2
|
+
import { Badge } from '../../ui/Badge'
|
|
2
3
|
import { Tooltip } from '../../ui/Tooltip'
|
|
4
|
+
import {
|
|
5
|
+
NETWORK_POLICY_PEER_DOTS,
|
|
6
|
+
NETWORK_POLICY_PEER_STYLES,
|
|
7
|
+
type NetworkPolicyPeerType,
|
|
8
|
+
} from './network-policy-peer-styles'
|
|
3
9
|
|
|
4
10
|
interface NetworkPolicyDiagramProps {
|
|
5
11
|
spec: any
|
|
12
|
+
staged?: boolean
|
|
6
13
|
}
|
|
7
14
|
|
|
8
15
|
/**
|
|
@@ -11,7 +18,7 @@ interface NetworkPolicyDiagramProps {
|
|
|
11
18
|
* Each rule is a horizontal band. Peers within a rule are OR'd (stacked).
|
|
12
19
|
* Pure CSS/SVG — no ReactFlow needed for this static diagram.
|
|
13
20
|
*/
|
|
14
|
-
export function NetworkPolicyDiagram({ spec }: NetworkPolicyDiagramProps) {
|
|
21
|
+
export function NetworkPolicyDiagram({ spec, staged = false }: NetworkPolicyDiagramProps) {
|
|
15
22
|
const podSelector = spec.podSelector || {}
|
|
16
23
|
const matchLabels = podSelector.matchLabels || {}
|
|
17
24
|
const policyTypes: string[] = spec.policyTypes || []
|
|
@@ -29,6 +36,13 @@ export function NetworkPolicyDiagram({ spec }: NetworkPolicyDiagramProps) {
|
|
|
29
36
|
|
|
30
37
|
return (
|
|
31
38
|
<div className="card-inner-lg space-y-3">
|
|
39
|
+
{staged && (
|
|
40
|
+
<div className="flex items-center gap-2 text-[11px] text-theme-text-tertiary">
|
|
41
|
+
<Badge severity="warning" size="sm">Staged preview</Badge>
|
|
42
|
+
<span>Dashed paths are evaluated but not enforced</span>
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
|
|
32
46
|
{/* Ingress flows */}
|
|
33
47
|
{hasIngress && (
|
|
34
48
|
<div className="space-y-2">
|
|
@@ -43,6 +57,7 @@ export function NetworkPolicyDiagram({ spec }: NetworkPolicyDiagramProps) {
|
|
|
43
57
|
ports={[]}
|
|
44
58
|
direction="ingress"
|
|
45
59
|
denied
|
|
60
|
+
staged={staged}
|
|
46
61
|
/>
|
|
47
62
|
) : (
|
|
48
63
|
ingress?.map((rule, i) => (
|
|
@@ -52,6 +67,7 @@ export function NetworkPolicyDiagram({ spec }: NetworkPolicyDiagramProps) {
|
|
|
52
67
|
target={targetLabel}
|
|
53
68
|
ports={extractPorts(rule.ports)}
|
|
54
69
|
direction="ingress"
|
|
70
|
+
staged={staged}
|
|
55
71
|
/>
|
|
56
72
|
))
|
|
57
73
|
)}
|
|
@@ -72,6 +88,7 @@ export function NetworkPolicyDiagram({ spec }: NetworkPolicyDiagramProps) {
|
|
|
72
88
|
ports={[]}
|
|
73
89
|
direction="egress"
|
|
74
90
|
denied
|
|
91
|
+
staged={staged}
|
|
75
92
|
/>
|
|
76
93
|
) : (
|
|
77
94
|
egress?.map((rule, i) => (
|
|
@@ -81,6 +98,7 @@ export function NetworkPolicyDiagram({ spec }: NetworkPolicyDiagramProps) {
|
|
|
81
98
|
target={targetLabel}
|
|
82
99
|
ports={extractPorts(rule.ports)}
|
|
83
100
|
direction="egress"
|
|
101
|
+
staged={staged}
|
|
84
102
|
/>
|
|
85
103
|
))
|
|
86
104
|
)}
|
|
@@ -97,7 +115,7 @@ export function NetworkPolicyDiagram({ spec }: NetworkPolicyDiagramProps) {
|
|
|
97
115
|
interface PeerInfo {
|
|
98
116
|
label: string
|
|
99
117
|
sublabel?: string
|
|
100
|
-
type:
|
|
118
|
+
type: NetworkPolicyPeerType
|
|
101
119
|
}
|
|
102
120
|
|
|
103
121
|
function extractPeers(peers: any[] | undefined): PeerInfo[] {
|
|
@@ -155,37 +173,20 @@ function formatSelector(selector: any): string {
|
|
|
155
173
|
return Object.entries(labels).map(([k, v]) => `${k}=${v}`).join(', ')
|
|
156
174
|
}
|
|
157
175
|
|
|
158
|
-
// Color coding for peer types
|
|
159
|
-
const PEER_STYLES = {
|
|
160
|
-
pod: 'border-emerald-500/30 bg-emerald-500/8',
|
|
161
|
-
namespace: 'border-sky-500/30 bg-sky-500/8',
|
|
162
|
-
cidr: 'border-amber-500/30 bg-amber-500/8',
|
|
163
|
-
combined: 'border-emerald-500/30 bg-emerald-500/8',
|
|
164
|
-
all: 'border-theme-border bg-theme-elevated/50',
|
|
165
|
-
deny: 'border-red-500/30 bg-red-500/8',
|
|
166
|
-
} as const
|
|
167
|
-
|
|
168
|
-
const PEER_DOT = {
|
|
169
|
-
pod: 'bg-emerald-500',
|
|
170
|
-
namespace: 'bg-sky-500',
|
|
171
|
-
cidr: 'bg-amber-500',
|
|
172
|
-
combined: 'bg-emerald-500',
|
|
173
|
-
all: 'bg-theme-text-tertiary',
|
|
174
|
-
deny: 'bg-red-500',
|
|
175
|
-
} as const
|
|
176
|
-
|
|
177
176
|
function FlowRow({
|
|
178
177
|
sources,
|
|
179
178
|
target,
|
|
180
179
|
ports,
|
|
181
180
|
direction,
|
|
182
181
|
denied = false,
|
|
182
|
+
staged = false,
|
|
183
183
|
}: {
|
|
184
184
|
sources: PeerInfo[]
|
|
185
185
|
target: string
|
|
186
186
|
ports: string[]
|
|
187
187
|
direction: 'ingress' | 'egress'
|
|
188
188
|
denied?: boolean
|
|
189
|
+
staged?: boolean
|
|
189
190
|
}) {
|
|
190
191
|
const isIngress = direction === 'ingress'
|
|
191
192
|
|
|
@@ -200,11 +201,11 @@ function FlowRow({
|
|
|
200
201
|
<div
|
|
201
202
|
className={clsx(
|
|
202
203
|
'rounded-md border px-2 py-1.5 min-w-0 overflow-hidden',
|
|
203
|
-
|
|
204
|
+
NETWORK_POLICY_PEER_STYLES[peer.type],
|
|
204
205
|
)}
|
|
205
206
|
>
|
|
206
207
|
<div className="flex items-center gap-1.5 min-w-0">
|
|
207
|
-
<span className={clsx('w-1.5 h-1.5 rounded-full shrink-0',
|
|
208
|
+
<span className={clsx('w-1.5 h-1.5 rounded-full shrink-0', NETWORK_POLICY_PEER_DOTS[peer.type])} />
|
|
208
209
|
<span className={clsx('text-[11px] font-medium truncate min-w-0', denied && 'line-through text-red-400')}>
|
|
209
210
|
{peer.label}
|
|
210
211
|
</span>
|
|
@@ -245,14 +246,14 @@ function FlowRow({
|
|
|
245
246
|
x1="2" y1="10" x2="32" y2="10"
|
|
246
247
|
stroke={denied ? '#ef4444' : direction === 'ingress' ? '#3b82f6' : '#a855f7'}
|
|
247
248
|
strokeWidth="1.5"
|
|
248
|
-
strokeDasharray={denied ? '3 2' : undefined}
|
|
249
|
+
strokeDasharray={staged ? '4 3' : denied ? '3 2' : undefined}
|
|
249
250
|
markerEnd={`url(#arrow-${direction}-${denied ? 'denied' : 'ok'})`}
|
|
250
251
|
/>
|
|
251
252
|
</svg>
|
|
252
253
|
{ports.length > 0 && (
|
|
253
|
-
<div className="flex flex-
|
|
254
|
+
<div className="flex flex-col items-center gap-0.5 mt-0.5">
|
|
254
255
|
{ports.map((p, i) => (
|
|
255
|
-
<span key={i} className="text-[8px] text-theme-text-tertiary font-mono leading-none">{p}</span>
|
|
256
|
+
<span key={i} className="whitespace-nowrap text-[8px] text-theme-text-tertiary font-mono leading-none">{p}</span>
|
|
256
257
|
))}
|
|
257
258
|
</div>
|
|
258
259
|
)}
|
|
@@ -5,9 +5,10 @@ import { NetworkPolicyDiagram } from './NetworkPolicyDiagram'
|
|
|
5
5
|
|
|
6
6
|
interface NetworkPolicyRendererProps {
|
|
7
7
|
data: any
|
|
8
|
+
staged?: boolean
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export function NetworkPolicyRenderer({ data }: NetworkPolicyRendererProps) {
|
|
11
|
+
export function NetworkPolicyRenderer({ data, staged = false }: NetworkPolicyRendererProps) {
|
|
11
12
|
const spec = data.spec || {}
|
|
12
13
|
const podSelector = spec.podSelector || {}
|
|
13
14
|
const policyTypes: string[] = spec.policyTypes || []
|
|
@@ -23,7 +24,7 @@ export function NetworkPolicyRenderer({ data }: NetworkPolicyRendererProps) {
|
|
|
23
24
|
<>
|
|
24
25
|
{hasDiagramContent && (
|
|
25
26
|
<Section title="Policy Flow" icon={GitFork} defaultExpanded>
|
|
26
|
-
<NetworkPolicyDiagram spec={spec} />
|
|
27
|
+
<NetworkPolicyDiagram spec={spec} staged={staged} />
|
|
27
28
|
</Section>
|
|
28
29
|
)}
|
|
29
30
|
|
|
@@ -125,7 +126,9 @@ function IngressEgressRuleCard({
|
|
|
125
126
|
)}
|
|
126
127
|
|
|
127
128
|
{peers.length === 0 && ports.length === 0 && (
|
|
128
|
-
<div className="text-xs text-theme-text-tertiary">
|
|
129
|
+
<div className="text-xs text-theme-text-tertiary">
|
|
130
|
+
{direction === 'from' ? 'All sources' : 'All destinations'}
|
|
131
|
+
</div>
|
|
129
132
|
)}
|
|
130
133
|
</div>
|
|
131
134
|
)
|