@skyhook-io/k8s-ui 1.8.5 → 1.8.6
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 +1 -1
- package/src/components/resources/get-pod-phase-display.test.ts +115 -3
- package/src/components/resources/renderers/CronJobRenderer.tsx +14 -7
- package/src/components/resources/renderers/EndpointSliceRenderer.tsx +4 -3
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +9 -15
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +3 -11
- package/src/components/resources/renderers/GatewayRenderer.tsx +11 -35
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +10 -16
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +3 -6
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +24 -28
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +19 -26
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +20 -19
- package/src/components/resources/renderers/JobRenderer.tsx +6 -5
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +9 -11
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +6 -12
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +11 -10
- package/src/components/resources/renderers/TraefikMiddlewareRenderer.tsx +185 -0
- package/src/components/resources/renderers/TraefikServersTransportRenderer.tsx +84 -0
- package/src/components/resources/renderers/TraefikServiceRenderer.tsx +118 -0
- package/src/components/resources/renderers/TraefikTLSOptionRenderer.tsx +62 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +1 -1
- package/src/components/resources/renderers/badge-no-handrolled.test.tsx +66 -0
- package/src/components/resources/renderers/contour-cells.tsx +6 -5
- package/src/components/resources/renderers/index.ts +4 -0
- package/src/components/resources/renderers/istio-cells.tsx +16 -25
- package/src/components/resources/resource-utils-cnpg.ts +1 -1
- package/src/components/resources/resource-utils-keda.ts +12 -6
- package/src/components/resources/resource-utils.ts +172 -57
- package/src/components/shared/ResourceRendererDispatch.tsx +10 -0
- package/src/components/topology/TopologyControls.tsx +41 -9
- package/src/components/ui/Badge.tsx +66 -3
- package/src/components/ui/ClusterName.tsx +12 -4
- package/src/components/ui/PaneLoader.tsx +6 -1
- package/src/types/core.ts +37 -0
- package/src/utils/asset-url.ts +13 -0
package/package.json
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { getPodPhaseDisplay } from './resource-utils'
|
|
2
|
+
import { getPodPhaseDisplay, getPodStatus } from './resource-utils'
|
|
3
|
+
|
|
4
|
+
describe('getPodStatus', () => {
|
|
5
|
+
it('keeps a Succeeded pod neutral despite a sidecar that OOMed before completion', () => {
|
|
6
|
+
const succeededWithOomedSidecar = {
|
|
7
|
+
status: {
|
|
8
|
+
phase: 'Succeeded',
|
|
9
|
+
containerStatuses: [
|
|
10
|
+
{ name: 'main', ready: false, state: { terminated: { reason: 'Completed', exitCode: 0 } } },
|
|
11
|
+
{ name: 'sidecar', ready: false, state: { terminated: { reason: 'OOMKilled', exitCode: 137 } } },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
const r = getPodStatus(succeededWithOomedSidecar)
|
|
16
|
+
expect(r.text).toBe('Completed')
|
|
17
|
+
expect(r.level).toBe('neutral')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('still flags a crash-looping (non-terminal) pod as unhealthy', () => {
|
|
21
|
+
const crashing = {
|
|
22
|
+
status: {
|
|
23
|
+
phase: 'Running',
|
|
24
|
+
containerStatuses: [{ name: 'app', ready: false, state: { waiting: { reason: 'CrashLoopBackOff' } } }],
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
expect(getPodStatus(crashing).level).toBe('unhealthy')
|
|
28
|
+
})
|
|
29
|
+
})
|
|
3
30
|
|
|
4
31
|
const podRunningHealthy = {
|
|
5
32
|
status: {
|
|
@@ -192,12 +219,59 @@ describe('getPodPhaseDisplay', () => {
|
|
|
192
219
|
expect(r.level).toBe('unhealthy')
|
|
193
220
|
})
|
|
194
221
|
|
|
195
|
-
it('marks
|
|
222
|
+
it('marks a long-terminating pod as Terminating + degraded (stuck shutdown)', () => {
|
|
196
223
|
const r = getPodPhaseDisplay(podTerminating)
|
|
197
224
|
expect(r.text).toBe('Running — Terminating')
|
|
198
225
|
expect(r.level).toBe('degraded')
|
|
199
226
|
})
|
|
200
227
|
|
|
228
|
+
it('keeps a recently-terminating pod neutral (graceful shutdown in progress)', () => {
|
|
229
|
+
const recent = {
|
|
230
|
+
metadata: { deletionTimestamp: new Date(Date.now() - 30 * 1000).toISOString() },
|
|
231
|
+
status: { phase: 'Running', containerStatuses: [{ name: 'app', ready: true, restartCount: 0 }] },
|
|
232
|
+
}
|
|
233
|
+
const r = getPodPhaseDisplay(recent)
|
|
234
|
+
expect(r.text).toBe('Running — Terminating')
|
|
235
|
+
expect(r.level).toBe('neutral')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('treats a completing pod (container exited 0, not ready) as healthy', () => {
|
|
239
|
+
const completing = {
|
|
240
|
+
status: {
|
|
241
|
+
phase: 'Running',
|
|
242
|
+
containerStatuses: [
|
|
243
|
+
{ name: 'app', ready: false, restartCount: 0, state: { terminated: { reason: 'Completed', exitCode: 0 } } },
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
}
|
|
247
|
+
const r = getPodPhaseDisplay(completing)
|
|
248
|
+
expect(r.text).toBe('Running')
|
|
249
|
+
expect(r.level).toBe('healthy')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('keeps a Failed pod unhealthy even while it is terminating', () => {
|
|
253
|
+
const failedTerminating = {
|
|
254
|
+
metadata: { deletionTimestamp: new Date().toISOString() },
|
|
255
|
+
status: { phase: 'Failed' },
|
|
256
|
+
}
|
|
257
|
+
const r = getPodPhaseDisplay(failedTerminating)
|
|
258
|
+
expect(r.text).toBe('Failed')
|
|
259
|
+
expect(r.level).toBe('unhealthy')
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it('surfaces a fatal container failure even while the pod is terminating', () => {
|
|
263
|
+
const terminatingCrash = {
|
|
264
|
+
metadata: { deletionTimestamp: new Date().toISOString() },
|
|
265
|
+
status: {
|
|
266
|
+
phase: 'Running',
|
|
267
|
+
containerStatuses: [{ name: 'app', ready: false, state: { waiting: { reason: 'CrashLoopBackOff' } } }],
|
|
268
|
+
},
|
|
269
|
+
}
|
|
270
|
+
const r = getPodPhaseDisplay(terminatingCrash)
|
|
271
|
+
expect(r.text).toBe('Running — CrashLoopBackOff')
|
|
272
|
+
expect(r.level).toBe('unhealthy')
|
|
273
|
+
})
|
|
274
|
+
|
|
201
275
|
it('falls back to Unknown for missing/unknown phase', () => {
|
|
202
276
|
expect(getPodPhaseDisplay({}).level).toBe('unknown')
|
|
203
277
|
expect(getPodPhaseDisplay({}).phase).toBe('Unknown')
|
|
@@ -206,10 +280,48 @@ describe('getPodPhaseDisplay', () => {
|
|
|
206
280
|
|
|
207
281
|
it('handles Succeeded/Pending/Failed phases', () => {
|
|
208
282
|
expect(getPodPhaseDisplay({ status: { phase: 'Succeeded' } }).level).toBe('neutral')
|
|
209
|
-
|
|
283
|
+
// A freshly-created Pending pod is benign (neutral), not degraded.
|
|
284
|
+
expect(getPodPhaseDisplay({ status: { phase: 'Pending' } }).level).toBe('neutral')
|
|
210
285
|
expect(getPodPhaseDisplay({ status: { phase: 'Failed' } }).level).toBe('unhealthy')
|
|
211
286
|
})
|
|
212
287
|
|
|
288
|
+
it('flags Unschedulable immediately, but gives plain young Pending a grace window', () => {
|
|
289
|
+
// The scheduler tried and failed — surface it now, no grace (matches backend).
|
|
290
|
+
const unsched = {
|
|
291
|
+
status: { phase: 'Pending', conditions: [{ type: 'PodScheduled', status: 'False', reason: 'Unschedulable' }] },
|
|
292
|
+
}
|
|
293
|
+
expect(getPodPhaseDisplay(unsched).text).toBe('Pending — Unschedulable')
|
|
294
|
+
expect(getPodPhaseDisplay(unsched).level).toBe('degraded')
|
|
295
|
+
|
|
296
|
+
// Plain Pending (not yet placed) is benign while young, degraded once stuck.
|
|
297
|
+
expect(getPodPhaseDisplay({ status: { phase: 'Pending' } }).level).toBe('neutral')
|
|
298
|
+
const stuckPending = {
|
|
299
|
+
metadata: { creationTimestamp: new Date(Date.now() - 10 * 60 * 1000).toISOString() },
|
|
300
|
+
status: { phase: 'Pending' },
|
|
301
|
+
}
|
|
302
|
+
expect(getPodPhaseDisplay(stuckPending).level).toBe('degraded')
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('flags a failing init container immediately, even on a young Pending pod', () => {
|
|
306
|
+
const initFail = {
|
|
307
|
+
metadata: { creationTimestamp: new Date().toISOString() },
|
|
308
|
+
status: {
|
|
309
|
+
phase: 'Pending',
|
|
310
|
+
initContainerStatuses: [{ name: 'init', ready: false, state: { waiting: { reason: 'ImagePullBackOff' } } }],
|
|
311
|
+
},
|
|
312
|
+
}
|
|
313
|
+
const r = getPodPhaseDisplay(initFail)
|
|
314
|
+
expect(r.text).toBe('Pending — ImagePullBackOff')
|
|
315
|
+
expect(r.level).toBe('unhealthy')
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('flags broadened fatal reasons like InvalidImageName', () => {
|
|
319
|
+
const bad = {
|
|
320
|
+
status: { phase: 'Pending', containerStatuses: [{ name: 'app', state: { waiting: { reason: 'InvalidImageName' } } }] },
|
|
321
|
+
}
|
|
322
|
+
expect(getPodPhaseDisplay(bad).level).toBe('unhealthy')
|
|
323
|
+
})
|
|
324
|
+
|
|
213
325
|
it('flags ErrImagePull as unhealthy', () => {
|
|
214
326
|
const r = getPodPhaseDisplay(podErrImagePull)
|
|
215
327
|
expect(r.text).toBe('Pending — ErrImagePull')
|
|
@@ -15,17 +15,24 @@ export function CronJobRenderer({ data, onNavigate }: CronJobRendererProps) {
|
|
|
15
15
|
const isSuspended = spec.suspend === true
|
|
16
16
|
const hasNeverRun = !status.lastScheduleTime
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
// "Recent failures" means the latest scheduled run didn't reach success.
|
|
19
|
+
// Only meaningful once nothing is running — while a job is active the most
|
|
20
|
+
// recent run is simply still in flight, not failing. With concurrencyPolicy
|
|
21
|
+
// Allow an older failed run can overlap a new active one; CronJob status only
|
|
22
|
+
// carries aggregate timestamps, so we accept missing that rare case here (the
|
|
23
|
+
// failed Job still surfaces in the job list / topology) rather than reviving
|
|
24
|
+
// the false positive that fired on every normal in-flight run.
|
|
25
|
+
const activeJobs = status.active?.length || 0
|
|
26
|
+
const lastSchedule = status.lastScheduleTime ? new Date(status.lastScheduleTime).getTime() : 0
|
|
27
|
+
const lastSuccess = status.lastSuccessfulTime ? new Date(status.lastSuccessfulTime).getTime() : 0
|
|
28
|
+
const recentFailures = activeJobs === 0 && lastSchedule > 0 && lastSchedule > lastSuccess
|
|
22
29
|
|
|
23
30
|
return (
|
|
24
31
|
<>
|
|
25
|
-
{/* Suspended
|
|
32
|
+
{/* Suspended is an intentional operator state, not a fault — keep it informational. */}
|
|
26
33
|
{isSuspended && (
|
|
27
34
|
<AlertBanner
|
|
28
|
-
variant="
|
|
35
|
+
variant="info"
|
|
29
36
|
icon={Pause}
|
|
30
37
|
title="CronJob Suspended"
|
|
31
38
|
message="No new jobs will be scheduled until this CronJob is resumed."
|
|
@@ -42,7 +49,7 @@ export function CronJobRenderer({ data, onNavigate }: CronJobRendererProps) {
|
|
|
42
49
|
)}
|
|
43
50
|
|
|
44
51
|
{/* Recent failures warning */}
|
|
45
|
-
{recentFailures && (
|
|
52
|
+
{recentFailures && !isSuspended && (
|
|
46
53
|
<AlertBanner
|
|
47
54
|
variant="error"
|
|
48
55
|
title="Recent Jobs Failing"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Radio, Server, Waypoints } from 'lucide-react'
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
4
|
+
import { Badge } from '../../ui/Badge'
|
|
4
5
|
import type { ResourceRef } from '../../../types'
|
|
5
6
|
|
|
6
7
|
interface EndpointSliceRendererProps {
|
|
@@ -69,7 +70,7 @@ export function EndpointSliceRenderer({ data, onNavigate }: EndpointSliceRendere
|
|
|
69
70
|
)}
|
|
70
71
|
</div>
|
|
71
72
|
<div className="flex items-center gap-2 shrink-0">
|
|
72
|
-
<
|
|
73
|
+
<Badge size="sm" protocol={port.protocol || 'TCP'}>{port.protocol || 'TCP'}</Badge>
|
|
73
74
|
<span className="font-mono text-theme-text-secondary">{port.port ?? '-'}</span>
|
|
74
75
|
</div>
|
|
75
76
|
</div>
|
|
@@ -92,9 +93,9 @@ export function EndpointSliceRenderer({ data, onNavigate }: EndpointSliceRendere
|
|
|
92
93
|
<div className="min-w-0 space-y-1">
|
|
93
94
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
94
95
|
{(endpoint.addresses || []).map((address: string) => (
|
|
95
|
-
<
|
|
96
|
+
<Badge key={address} size="sm" tone="structural" className="font-mono">
|
|
96
97
|
{address}
|
|
97
|
-
</
|
|
98
|
+
</Badge>
|
|
98
99
|
))}
|
|
99
100
|
</div>
|
|
100
101
|
{targetLabel && (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Globe, ArrowRight, Network, Filter } from 'lucide-react'
|
|
2
|
-
import { clsx } from 'clsx'
|
|
3
2
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceRefBadge } from '../../ui/drawer-components'
|
|
3
|
+
import { Badge } from '../../ui/Badge'
|
|
4
4
|
import type { ResourceRef } from '../../../types'
|
|
5
5
|
|
|
6
6
|
interface GRPCRouteRendererProps {
|
|
@@ -77,7 +77,7 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps)
|
|
|
77
77
|
hostnames.length > 0 ? (
|
|
78
78
|
<div className="flex flex-wrap gap-1">
|
|
79
79
|
{hostnames.map((h: string) => (
|
|
80
|
-
<
|
|
80
|
+
<Badge key={h} tone="structural">{h}</Badge>
|
|
81
81
|
))}
|
|
82
82
|
</div>
|
|
83
83
|
) : (
|
|
@@ -150,7 +150,7 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps)
|
|
|
150
150
|
{match.method && (
|
|
151
151
|
<>
|
|
152
152
|
{match.method.type && match.method.type !== 'Exact' && (
|
|
153
|
-
<
|
|
153
|
+
<Badge tone="accent1" size="sm">{match.method.type}</Badge>
|
|
154
154
|
)}
|
|
155
155
|
{match.method.service && (
|
|
156
156
|
<span>
|
|
@@ -200,9 +200,9 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps)
|
|
|
200
200
|
<div className="text-xs text-theme-text-secondary flex flex-wrap items-center gap-1.5">
|
|
201
201
|
<Filter className="w-3 h-3 text-theme-text-tertiary" />
|
|
202
202
|
{filters.map((filter: any, filterIdx: number) => (
|
|
203
|
-
<
|
|
203
|
+
<Badge key={filterIdx} tone="accent1" size="sm">
|
|
204
204
|
{filter.type}
|
|
205
|
-
</
|
|
205
|
+
</Badge>
|
|
206
206
|
))}
|
|
207
207
|
</div>
|
|
208
208
|
</div>
|
|
@@ -231,20 +231,14 @@ export function GRPCRouteRenderer({ data, onNavigate }: GRPCRouteRendererProps)
|
|
|
231
231
|
</div>
|
|
232
232
|
<div className="flex flex-wrap gap-2">
|
|
233
233
|
{accepted && (
|
|
234
|
-
<
|
|
235
|
-
'badge',
|
|
236
|
-
accepted.status === 'True' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
|
|
237
|
-
)}>
|
|
234
|
+
<Badge severity={accepted.status === 'True' ? 'success' : 'error'}>
|
|
238
235
|
{accepted.status === 'True' ? 'Accepted' : 'Not Accepted'}
|
|
239
|
-
</
|
|
236
|
+
</Badge>
|
|
240
237
|
)}
|
|
241
238
|
{resolved && (
|
|
242
|
-
<
|
|
243
|
-
'badge',
|
|
244
|
-
resolved.status === 'True' ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'
|
|
245
|
-
)}>
|
|
239
|
+
<Badge severity={resolved.status === 'True' ? 'success' : 'warning'}>
|
|
246
240
|
{resolved.status === 'True' ? 'Refs Resolved' : 'Unresolved Refs'}
|
|
247
|
-
</
|
|
241
|
+
</Badge>
|
|
248
242
|
)}
|
|
249
243
|
</div>
|
|
250
244
|
{accepted?.message && accepted.status === 'False' && (
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Cpu } from 'lucide-react'
|
|
2
|
-
import { clsx } from 'clsx'
|
|
3
2
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner } from '../../ui/drawer-components'
|
|
4
|
-
import {
|
|
3
|
+
import { Badge } from '../../ui/Badge'
|
|
5
4
|
|
|
6
5
|
interface GatewayClassRendererProps {
|
|
7
6
|
data: any
|
|
@@ -35,16 +34,9 @@ export function GatewayClassRenderer({ data }: GatewayClassRendererProps) {
|
|
|
35
34
|
<Property
|
|
36
35
|
label="Accepted"
|
|
37
36
|
value={
|
|
38
|
-
<
|
|
39
|
-
'badge',
|
|
40
|
-
isAccepted
|
|
41
|
-
? 'bg-green-500/20 text-green-400'
|
|
42
|
-
: isNotAccepted
|
|
43
|
-
? 'bg-red-500/20 text-red-400'
|
|
44
|
-
: BADGE_INACTIVE
|
|
45
|
-
)}>
|
|
37
|
+
<Badge severity={isAccepted ? 'success' : isNotAccepted ? 'error' : 'neutral'}>
|
|
46
38
|
{isAccepted ? 'True' : isNotAccepted ? 'False' : 'Unknown'}
|
|
47
|
-
</
|
|
39
|
+
</Badge>
|
|
48
40
|
}
|
|
49
41
|
/>
|
|
50
42
|
</PropertyList>
|
|
@@ -2,14 +2,7 @@ import { Globe, Radio, Lock } from 'lucide-react'
|
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceLink } from '../../ui/drawer-components'
|
|
4
4
|
import { BADGE_INACTIVE } from '../../../utils/badge-colors'
|
|
5
|
-
|
|
6
|
-
const protocolColors: Record<string, string> = {
|
|
7
|
-
HTTP: 'bg-blue-500/20 text-blue-400',
|
|
8
|
-
HTTPS: 'bg-green-500/20 text-green-400',
|
|
9
|
-
TLS: 'bg-green-500/20 text-green-400',
|
|
10
|
-
TCP: 'bg-orange-500/20 text-orange-400',
|
|
11
|
-
UDP: 'bg-purple-500/20 text-purple-400',
|
|
12
|
-
}
|
|
5
|
+
import { Badge } from '../../ui/Badge'
|
|
13
6
|
|
|
14
7
|
interface GatewayRendererProps {
|
|
15
8
|
data: any
|
|
@@ -68,31 +61,17 @@ export function GatewayRenderer({ data, onNavigate }: GatewayRendererProps) {
|
|
|
68
61
|
<Property
|
|
69
62
|
label="Accepted"
|
|
70
63
|
value={
|
|
71
|
-
<
|
|
72
|
-
'badge',
|
|
73
|
-
isAccepted
|
|
74
|
-
? 'bg-green-500/20 text-green-400'
|
|
75
|
-
: isNotAccepted
|
|
76
|
-
? 'bg-red-500/20 text-red-400'
|
|
77
|
-
: BADGE_INACTIVE
|
|
78
|
-
)}>
|
|
64
|
+
<Badge severity={isAccepted ? 'success' : isNotAccepted ? 'error' : 'neutral'}>
|
|
79
65
|
{isAccepted ? 'True' : isNotAccepted ? 'False' : 'Unknown'}
|
|
80
|
-
</
|
|
66
|
+
</Badge>
|
|
81
67
|
}
|
|
82
68
|
/>
|
|
83
69
|
<Property
|
|
84
70
|
label="Programmed"
|
|
85
71
|
value={
|
|
86
|
-
<
|
|
87
|
-
'badge',
|
|
88
|
-
isProgrammed
|
|
89
|
-
? 'bg-green-500/20 text-green-400'
|
|
90
|
-
: isNotProgrammed
|
|
91
|
-
? 'bg-red-500/20 text-red-400'
|
|
92
|
-
: BADGE_INACTIVE
|
|
93
|
-
)}>
|
|
72
|
+
<Badge severity={isProgrammed ? 'success' : isNotProgrammed ? 'error' : 'neutral'}>
|
|
94
73
|
{isProgrammed ? 'True' : isNotProgrammed ? 'False' : 'Unknown'}
|
|
95
|
-
</
|
|
74
|
+
</Badge>
|
|
96
75
|
}
|
|
97
76
|
/>
|
|
98
77
|
</PropertyList>
|
|
@@ -133,23 +112,20 @@ export function GatewayRenderer({ data, onNavigate }: GatewayRendererProps) {
|
|
|
133
112
|
<div className="flex items-center gap-2">
|
|
134
113
|
{isHTTPS && <Lock className="w-3.5 h-3.5 text-green-400" />}
|
|
135
114
|
<span className="text-sm font-medium text-theme-text-primary">{listener.name}</span>
|
|
136
|
-
<
|
|
137
|
-
'px-1.5 py-0.5 rounded text-[10px] font-medium',
|
|
138
|
-
protocolColors[listener.protocol] || BADGE_INACTIVE
|
|
139
|
-
)}>
|
|
115
|
+
<Badge protocol={listener.protocol} size="sm">
|
|
140
116
|
{listener.protocol}:{listener.port}
|
|
141
|
-
</
|
|
117
|
+
</Badge>
|
|
142
118
|
</div>
|
|
143
119
|
<div className="flex items-center gap-1">
|
|
144
120
|
{isConflicted && (
|
|
145
|
-
<
|
|
121
|
+
<Badge severity="error" size="sm">
|
|
146
122
|
Conflicted
|
|
147
|
-
</
|
|
123
|
+
</Badge>
|
|
148
124
|
)}
|
|
149
125
|
{hasUnresolvedRefs && (
|
|
150
|
-
<
|
|
126
|
+
<Badge severity="warning" size="sm">
|
|
151
127
|
Unresolved Refs
|
|
152
|
-
</
|
|
128
|
+
</Badge>
|
|
153
129
|
)}
|
|
154
130
|
{listenerAccepted && (
|
|
155
131
|
<span className={clsx(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Globe, ArrowRight, Network, Filter } from 'lucide-react'
|
|
2
|
-
import { clsx } from 'clsx'
|
|
3
2
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, ResourceRefBadge } from '../../ui/drawer-components'
|
|
3
|
+
import { Badge } from '../../ui/Badge'
|
|
4
4
|
import type { ResourceRef } from '../../../types'
|
|
5
5
|
|
|
6
6
|
interface HTTPRouteRendererProps {
|
|
@@ -77,7 +77,7 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps)
|
|
|
77
77
|
hostnames.length > 0 ? (
|
|
78
78
|
<div className="flex flex-wrap gap-1">
|
|
79
79
|
{hostnames.map((h: string) => (
|
|
80
|
-
<
|
|
80
|
+
<Badge key={h} tone="structural">{h}</Badge>
|
|
81
81
|
))}
|
|
82
82
|
</div>
|
|
83
83
|
) : (
|
|
@@ -148,9 +148,9 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps)
|
|
|
148
148
|
{matches.map((match: any, matchIdx: number) => (
|
|
149
149
|
<div key={matchIdx} className="text-xs text-theme-text-secondary flex flex-wrap items-center gap-1.5">
|
|
150
150
|
{match.method && (
|
|
151
|
-
<
|
|
151
|
+
<Badge tone="structural" size="sm">
|
|
152
152
|
{match.method}
|
|
153
|
-
</
|
|
153
|
+
</Badge>
|
|
154
154
|
)}
|
|
155
155
|
{match.path && (
|
|
156
156
|
<span>
|
|
@@ -201,9 +201,9 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps)
|
|
|
201
201
|
<Filter className="w-3 h-3 text-theme-text-tertiary" />
|
|
202
202
|
{filters.map((filter: any, filterIdx: number) => (
|
|
203
203
|
<span key={filterIdx} className="inline-flex items-center gap-1">
|
|
204
|
-
<
|
|
204
|
+
<Badge tone="accent1" size="sm">
|
|
205
205
|
{filter.type}
|
|
206
|
-
</
|
|
206
|
+
</Badge>
|
|
207
207
|
{filter.type === 'RequestHeaderModifier' && filter.requestHeaderModifier && (
|
|
208
208
|
<span className="text-theme-text-tertiary">
|
|
209
209
|
{summarizeHeaderModifier(filter.requestHeaderModifier)}
|
|
@@ -263,20 +263,14 @@ export function HTTPRouteRenderer({ data, onNavigate }: HTTPRouteRendererProps)
|
|
|
263
263
|
</div>
|
|
264
264
|
<div className="flex flex-wrap gap-2">
|
|
265
265
|
{accepted && (
|
|
266
|
-
<
|
|
267
|
-
'badge',
|
|
268
|
-
accepted.status === 'True' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
|
|
269
|
-
)}>
|
|
266
|
+
<Badge severity={accepted.status === 'True' ? 'success' : 'error'}>
|
|
270
267
|
{accepted.status === 'True' ? 'Accepted' : 'Not Accepted'}
|
|
271
|
-
</
|
|
268
|
+
</Badge>
|
|
272
269
|
)}
|
|
273
270
|
{resolved && (
|
|
274
|
-
<
|
|
275
|
-
'badge',
|
|
276
|
-
resolved.status === 'True' ? 'bg-green-500/20 text-green-400' : 'bg-yellow-500/20 text-yellow-400'
|
|
277
|
-
)}>
|
|
271
|
+
<Badge severity={resolved.status === 'True' ? 'success' : 'warning'}>
|
|
278
272
|
{resolved.status === 'True' ? 'Refs Resolved' : 'Unresolved Refs'}
|
|
279
|
-
</
|
|
273
|
+
</Badge>
|
|
280
274
|
)}
|
|
281
275
|
</div>
|
|
282
276
|
{accepted?.message && accepted.status === 'False' && (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Database, CheckCircle2, Shield } from 'lucide-react'
|
|
2
|
-
import { clsx } from 'clsx'
|
|
3
2
|
import { Section, PropertyList, Property, ConditionsSection, ProblemAlerts } from '../../ui/drawer-components'
|
|
3
|
+
import { Badge } from '../../ui/Badge'
|
|
4
4
|
import { formatAge } from '../resource-utils'
|
|
5
5
|
import { formatBytes } from '../../../utils/format'
|
|
6
6
|
import { GitOpsStatusBadge, SyncCountdown } from '../../gitops'
|
|
@@ -58,12 +58,9 @@ export function HelmRepositoryRenderer({ data }: HelmRepositoryRendererProps) {
|
|
|
58
58
|
<Property
|
|
59
59
|
label="Type"
|
|
60
60
|
value={
|
|
61
|
-
<
|
|
62
|
-
'badge',
|
|
63
|
-
isOCI ? 'bg-purple-500/20 text-purple-400' : 'bg-blue-500/20 text-blue-400'
|
|
64
|
-
)}>
|
|
61
|
+
<Badge tone={isOCI ? 'accent2' : 'accent1'}>
|
|
65
62
|
{isOCI ? 'OCI' : 'HTTP'}
|
|
66
|
-
</
|
|
63
|
+
</Badge>
|
|
67
64
|
}
|
|
68
65
|
/>
|
|
69
66
|
{spec.provider && <Property label="Provider" value={spec.provider} />}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { Shield } from 'lucide-react'
|
|
2
|
-
import { clsx } from 'clsx'
|
|
3
2
|
import { Section, PropertyList, Property, ConditionsSection, AlertBanner, KeyValueBadgeList } from '../../ui/drawer-components'
|
|
3
|
+
import { Badge, type BadgeSeverity } from '../../ui/Badge'
|
|
4
4
|
import {
|
|
5
5
|
getAuthorizationPolicyAction,
|
|
6
6
|
getAuthorizationPolicyRules,
|
|
7
7
|
getAuthorizationPolicySelector,
|
|
8
8
|
} from '../resource-utils-istio'
|
|
9
|
-
import { BADGE_INACTIVE } from '../../../utils/badge-colors'
|
|
10
9
|
|
|
11
|
-
const
|
|
12
|
-
ALLOW: '
|
|
13
|
-
DENY: '
|
|
14
|
-
CUSTOM: '
|
|
15
|
-
AUDIT: '
|
|
10
|
+
const actionSeverity: Record<string, BadgeSeverity> = {
|
|
11
|
+
ALLOW: 'success',
|
|
12
|
+
DENY: 'error',
|
|
13
|
+
CUSTOM: 'info',
|
|
14
|
+
AUDIT: 'warning',
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
interface IstioAuthorizationPolicyRendererProps {
|
|
@@ -51,12 +50,9 @@ export function IstioAuthorizationPolicyRenderer({ data }: IstioAuthorizationPol
|
|
|
51
50
|
<Section title="Authorization Policy" icon={Shield} defaultExpanded>
|
|
52
51
|
<PropertyList>
|
|
53
52
|
<Property label="Action" value={
|
|
54
|
-
<
|
|
55
|
-
'badge',
|
|
56
|
-
actionColors[action] || BADGE_INACTIVE
|
|
57
|
-
)}>
|
|
53
|
+
<Badge severity={actionSeverity[action] ?? 'neutral'}>
|
|
58
54
|
{action}
|
|
59
|
-
</
|
|
55
|
+
</Badge>
|
|
60
56
|
} />
|
|
61
57
|
<Property label="Scope" value={hasSelector ? 'Workload-scoped' : 'Namespace-wide'} />
|
|
62
58
|
<Property label="Rules" value={String(rules.length)} />
|
|
@@ -88,24 +84,24 @@ export function IstioAuthorizationPolicyRenderer({ data }: IstioAuthorizationPol
|
|
|
88
84
|
{rule.from.map((f: any, fi: number) => (
|
|
89
85
|
<div key={fi} className="flex flex-wrap gap-1 mb-1">
|
|
90
86
|
{f.source?.principals?.map((p: string, pi: number) => (
|
|
91
|
-
<
|
|
87
|
+
<Badge key={`p-${pi}`} tone="structural" size="sm">
|
|
92
88
|
principal: {p}
|
|
93
|
-
</
|
|
89
|
+
</Badge>
|
|
94
90
|
))}
|
|
95
91
|
{f.source?.namespaces?.map((n: string, ni: number) => (
|
|
96
|
-
<
|
|
92
|
+
<Badge key={`n-${ni}`} tone="structural" size="sm">
|
|
97
93
|
namespace: {n}
|
|
98
|
-
</
|
|
94
|
+
</Badge>
|
|
99
95
|
))}
|
|
100
96
|
{f.source?.ipBlocks?.map((ip: string, ipi: number) => (
|
|
101
|
-
<
|
|
97
|
+
<Badge key={`ip-${ipi}`} tone="structural" size="sm">
|
|
102
98
|
IP: {ip}
|
|
103
|
-
</
|
|
99
|
+
</Badge>
|
|
104
100
|
))}
|
|
105
101
|
{f.source?.requestPrincipals?.map((rp: string, rpi: number) => (
|
|
106
|
-
<
|
|
102
|
+
<Badge key={`rp-${rpi}`} tone="structural" size="sm">
|
|
107
103
|
reqPrincipal: {rp}
|
|
108
|
-
</
|
|
104
|
+
</Badge>
|
|
109
105
|
))}
|
|
110
106
|
</div>
|
|
111
107
|
))}
|
|
@@ -119,24 +115,24 @@ export function IstioAuthorizationPolicyRenderer({ data }: IstioAuthorizationPol
|
|
|
119
115
|
{rule.to.map((t: any, ti: number) => (
|
|
120
116
|
<div key={ti} className="flex flex-wrap gap-1 mb-1">
|
|
121
117
|
{t.operation?.hosts?.map((h: string, hi: number) => (
|
|
122
|
-
<
|
|
118
|
+
<Badge key={`h-${hi}`} tone="structural" size="sm">
|
|
123
119
|
host: {h}
|
|
124
|
-
</
|
|
120
|
+
</Badge>
|
|
125
121
|
))}
|
|
126
122
|
{t.operation?.ports?.map((p: string, pi: number) => (
|
|
127
|
-
<
|
|
123
|
+
<Badge key={`p-${pi}`} tone="structural" size="sm">
|
|
128
124
|
port: {p}
|
|
129
|
-
</
|
|
125
|
+
</Badge>
|
|
130
126
|
))}
|
|
131
127
|
{t.operation?.methods?.map((m: string, mi: number) => (
|
|
132
|
-
<
|
|
128
|
+
<Badge key={`m-${mi}`} tone="structural" size="sm">
|
|
133
129
|
method: {m}
|
|
134
|
-
</
|
|
130
|
+
</Badge>
|
|
135
131
|
))}
|
|
136
132
|
{t.operation?.paths?.map((p: string, pi: number) => (
|
|
137
|
-
<
|
|
133
|
+
<Badge key={`path-${pi}`} tone="structural" size="sm">
|
|
138
134
|
path: {p}
|
|
139
|
-
</
|
|
135
|
+
</Badge>
|
|
140
136
|
))}
|
|
141
137
|
</div>
|
|
142
138
|
))}
|