@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.
Files changed (117) hide show
  1. package/package.json +4 -4
  2. package/src/components/applications/ApplicationDetail.tsx +2 -2
  3. package/src/components/cluster-switcher/ClusterSwitcher.tsx +32 -7
  4. package/src/components/issues/severity.ts +1 -0
  5. package/src/components/resources/ResourcesView.empty-note.test.ts +41 -0
  6. package/src/components/resources/ResourcesView.tsx +419 -9
  7. package/src/components/resources/index.ts +27 -0
  8. package/src/components/resources/kyverno-legacy-action.test.ts +98 -0
  9. package/src/components/resources/renderers/AnalysisRunRenderer.test.tsx +241 -0
  10. package/src/components/resources/renderers/AnalysisRunRenderer.tsx +166 -0
  11. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +31 -2
  12. package/src/components/resources/renderers/CNPGClusterRenderer.test.tsx +33 -0
  13. package/src/components/resources/renderers/CNPGClusterRenderer.tsx +47 -4
  14. package/src/components/resources/renderers/CNPGDeclarativeRenderer.tsx +243 -0
  15. package/src/components/resources/renderers/CNPGObjectStoreRenderer.test.tsx +99 -0
  16. package/src/components/resources/renderers/CNPGObjectStoreRenderer.tsx +235 -0
  17. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +23 -1
  18. package/src/components/resources/renderers/CNPGShared.tsx +27 -0
  19. package/src/components/resources/renderers/CalicoHostEndpointRenderer.tsx +71 -0
  20. package/src/components/resources/renderers/CalicoIPPoolRenderer.tsx +32 -0
  21. package/src/components/resources/renderers/CalicoNetworkPolicyDiagram.test.tsx +205 -0
  22. package/src/components/resources/renderers/CalicoNetworkPolicyDiagram.tsx +537 -0
  23. package/src/components/resources/renderers/CalicoNetworkPolicyRenderer.test.tsx +223 -0
  24. package/src/components/resources/renderers/CalicoNetworkPolicyRenderer.tsx +670 -0
  25. package/src/components/resources/renderers/CalicoTierRenderer.tsx +19 -0
  26. package/src/components/resources/renderers/KyvernoCELPolicyRenderers.tsx +13 -6
  27. package/src/components/resources/renderers/KyvernoExceptionRenderers.tsx +3 -2
  28. package/src/components/resources/renderers/KyvernoGlobalContextRenderer.test.ts +31 -0
  29. package/src/components/resources/renderers/KyvernoGlobalContextRenderer.tsx +124 -0
  30. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +28 -2
  31. package/src/components/resources/renderers/KyvernoQueueRenderers.tsx +303 -0
  32. package/src/components/resources/renderers/LookupFailureNote.test.tsx +60 -0
  33. package/src/components/resources/renderers/LookupFailureNote.tsx +42 -0
  34. package/src/components/resources/renderers/NamespaceRenderer.test.ts +30 -0
  35. package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -3
  36. package/src/components/resources/renderers/NetworkPolicyDiagram.tsx +27 -26
  37. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +6 -3
  38. package/src/components/resources/renderers/PolicyCoverageSection.test.tsx +348 -0
  39. package/src/components/resources/renderers/PolicyCoverageSection.tsx +939 -0
  40. package/src/components/resources/renderers/PolicySection.test.tsx +59 -2
  41. package/src/components/resources/renderers/PolicySection.tsx +32 -5
  42. package/src/components/resources/renderers/RolloutRenderer.test.tsx +491 -0
  43. package/src/components/resources/renderers/RolloutRenderer.tsx +391 -92
  44. package/src/components/resources/renderers/VeleroBSLRenderer.test.tsx +374 -0
  45. package/src/components/resources/renderers/VeleroBSLRenderer.tsx +179 -3
  46. package/src/components/resources/renderers/VeleroBackupRenderer.test.tsx +221 -0
  47. package/src/components/resources/renderers/VeleroBackupRenderer.tsx +119 -6
  48. package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.test.tsx +69 -0
  49. package/src/components/resources/renderers/VeleroBackupRepositoryRenderer.tsx +96 -0
  50. package/src/components/resources/renderers/VeleroRestoreRenderer.test.tsx +55 -0
  51. package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +22 -4
  52. package/src/components/resources/renderers/VeleroRunMessages.test.tsx +152 -0
  53. package/src/components/resources/renderers/VeleroRunMessages.tsx +139 -0
  54. package/src/components/resources/renderers/VeleroScheduleRenderer.test.tsx +88 -0
  55. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +16 -6
  56. package/src/components/resources/renderers/calico-cells.tsx +177 -0
  57. package/src/components/resources/renderers/cnpg-cells.tsx +100 -0
  58. package/src/components/resources/renderers/index.ts +16 -1
  59. package/src/components/resources/renderers/kyverno-cells.tsx +81 -0
  60. package/src/components/resources/renderers/network-policy-peer-styles.ts +25 -0
  61. package/src/components/resources/renderers/velero-cells.tsx +29 -7
  62. package/src/components/resources/renderers/velero-dead-ends.test.tsx +142 -0
  63. package/src/components/resources/resource-utils-calico.test.ts +215 -0
  64. package/src/components/resources/resource-utils-calico.ts +261 -0
  65. package/src/components/resources/resource-utils-cnpg-declarative.test.ts +104 -0
  66. package/src/components/resources/resource-utils-cnpg-objectstore.test.ts +122 -0
  67. package/src/components/resources/resource-utils-cnpg.test.ts +54 -0
  68. package/src/components/resources/resource-utils-cnpg.ts +264 -3
  69. package/src/components/resources/resource-utils-kyverno-modern.ts +24 -0
  70. package/src/components/resources/resource-utils-kyverno-queue.test.ts +164 -0
  71. package/src/components/resources/resource-utils-kyverno-queue.ts +198 -0
  72. package/src/components/resources/resource-utils-kyverno.ts +43 -7
  73. package/src/components/resources/resource-utils-velero.test.ts +26 -0
  74. package/src/components/resources/resource-utils-velero.ts +68 -0
  75. package/src/components/resources/resource-utils.ts +98 -0
  76. package/src/components/shared/ResourceActionsBar.tsx +101 -16
  77. package/src/components/shared/ResourceRendererDispatch.test.tsx +379 -3
  78. package/src/components/shared/ResourceRendererDispatch.tsx +199 -22
  79. package/src/components/shared/index.ts +1 -1
  80. package/src/components/shared/revision-role-badges.test.ts +120 -0
  81. package/src/components/topology/K8sResourceNode.test.ts +10 -0
  82. package/src/components/topology/K8sResourceNode.tsx +22 -0
  83. package/src/components/topology/TopologyFilterSidebar.test.tsx +31 -0
  84. package/src/components/topology/TopologyFilterSidebar.tsx +5 -0
  85. package/src/components/topology/TopologyGraph.tsx +6 -6
  86. package/src/components/topology/TopologySearch.tsx +7 -0
  87. package/src/components/topology/layout.ts +3 -0
  88. package/src/components/trace/ReachabilityView.tsx +1 -0
  89. package/src/components/trace/TracePanel.tsx +13 -2
  90. package/src/components/trace/reachFixtures.ts +26 -1
  91. package/src/components/trace/reachGraphModel.test.ts +20 -0
  92. package/src/components/trace/reachGraphModel.ts +8 -1
  93. package/src/components/trace/reachOrigins.test.ts +31 -0
  94. package/src/components/trace/reachOrigins.ts +11 -1
  95. package/src/components/ui/Badge.tsx +10 -0
  96. package/src/components/ui/InClusterConsentDialog.tsx +5 -4
  97. package/src/components/ui/ScheduleValue.test.tsx +80 -0
  98. package/src/components/ui/ScheduleValue.tsx +85 -0
  99. package/src/components/ui/SelectMenu.tsx +202 -28
  100. package/src/components/ui/drawer-components.test.tsx +17 -1
  101. package/src/components/ui/drawer-components.tsx +9 -2
  102. package/src/components/ui/index.ts +2 -0
  103. package/src/components/workload/WorkloadView.tsx +5 -2
  104. package/src/types/core.ts +36 -4
  105. package/src/types/policy.ts +153 -2
  106. package/src/utils/clipboard.test.ts +86 -0
  107. package/src/utils/clipboard.ts +50 -0
  108. package/src/utils/extended-resources.ts +4 -7
  109. package/src/utils/format.test.ts +83 -1
  110. package/src/utils/format.ts +80 -1
  111. package/src/utils/inClusterConsent.test.ts +9 -0
  112. package/src/utils/inClusterConsent.ts +9 -9
  113. package/src/utils/index.ts +1 -0
  114. package/src/utils/navigation.test.ts +5 -0
  115. package/src/utils/navigation.ts +5 -0
  116. package/src/utils/resource-icons.test.ts +49 -2
  117. 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
- function quotaUsageRatio(resourceName: string, used: string, hard: string): number | null {
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 : (v: string) => parseFloat(v) || 0
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: 'pod' | 'namespace' | 'cidr' | 'all' | 'deny' | 'combined'
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
- PEER_STYLES[peer.type],
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', PEER_DOT[peer.type])} />
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-wrap justify-center gap-0.5 mt-0.5">
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">Allow all</div>
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
  )