@skyhook-io/k8s-ui 1.10.0 → 1.10.2
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/issues/types.ts +6 -0
- package/src/components/resources/renderers/ContainerEnvironmentSection.tsx +594 -0
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.test.tsx +26 -1
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +34 -15
- package/src/components/resources/renderers/PodRenderer.test.tsx +227 -0
- package/src/components/resources/renderers/PodRenderer.tsx +63 -2
- package/src/components/resources/renderers/index.ts +1 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +6 -1
- package/src/components/ui/drawer-components.tsx +3 -2
- package/src/components/ui/index.ts +3 -1
- package/src/components/workload/WorkloadView.tsx +22 -0
- package/src/types/capacity.ts +702 -0
- package/src/types/core.ts +72 -2
- package/src/types/index.ts +1 -0
- package/src/utils/format.test.ts +35 -0
- package/src/utils/format.ts +6 -1
- package/src/utils/navigation.test.ts +9 -0
- package/src/utils/navigation.ts +4 -2
package/src/types/core.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { CapacityIntegrationState } from './capacity'
|
|
2
|
+
|
|
1
3
|
// Topology types matching the Go backend
|
|
2
4
|
|
|
3
5
|
// Per-resource-type RBAC permissions. Field names must match the JSON keys
|
|
@@ -57,6 +59,12 @@ export interface WorkloadWritePermissions {
|
|
|
57
59
|
rollouts: boolean
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
export interface IntegrationCapability {
|
|
63
|
+
state: CapacityIntegrationState
|
|
64
|
+
reasonCode?: string
|
|
65
|
+
cacheUnavailable?: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
// Feature capabilities based on RBAC permissions
|
|
61
69
|
export interface Capabilities {
|
|
62
70
|
exec: boolean // Terminal feature (pods/exec)
|
|
@@ -69,6 +77,10 @@ export interface Capabilities {
|
|
|
69
77
|
nodeWrite: boolean // Node write operations (cordon, uncordon, drain)
|
|
70
78
|
workloadWrites?: WorkloadWritePermissions // Workload patch permissions (restart/scale controls)
|
|
71
79
|
mcpEnabled: boolean // MCP server is running
|
|
80
|
+
// Karpenter discovery and NodePool read state. Optional on the wire for the
|
|
81
|
+
// same newer-frontend/older-backend reason as `deployment` below — consumers
|
|
82
|
+
// must treat absence as "unknown" and fall back to discovery signals.
|
|
83
|
+
karpenter?: IntegrationCapability
|
|
72
84
|
// How / where this Radar binary is running. Optional on the wire so a
|
|
73
85
|
// newer frontend (e.g. radar-hub-web bundling a fresher @skyhook-io/radar-app)
|
|
74
86
|
// doesn't crash against an older backend that hasn't shipped the field yet —
|
|
@@ -462,6 +474,62 @@ export interface ResolvedEnvFromEntry {
|
|
|
462
474
|
export type ResolvedEnvFromKey = `configmap:${string}` | `secret:${string}`
|
|
463
475
|
export type ResolvedEnvFrom = Partial<Record<ResolvedEnvFromKey, ResolvedEnvFromEntry>>
|
|
464
476
|
|
|
477
|
+
export type PodEnvironmentValueState = 'resolved' | 'masked' | 'unavailable' | 'missing' | 'denied'
|
|
478
|
+
|
|
479
|
+
export interface PodEnvironmentSource {
|
|
480
|
+
kind: string
|
|
481
|
+
name?: string
|
|
482
|
+
key?: string
|
|
483
|
+
variable?: string
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export interface PodEnvironmentEvidence {
|
|
487
|
+
kind: 'modified' | 'removed' | 'added'
|
|
488
|
+
changedAt: string
|
|
489
|
+
message?: string
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export interface PodEnvironmentRow {
|
|
493
|
+
name: string
|
|
494
|
+
value?: string
|
|
495
|
+
state: PodEnvironmentValueState
|
|
496
|
+
sensitive?: boolean
|
|
497
|
+
source: PodEnvironmentSource
|
|
498
|
+
dependencies?: PodEnvironmentSource[]
|
|
499
|
+
shadowedSources?: PodEnvironmentSource[]
|
|
500
|
+
message?: string
|
|
501
|
+
optional?: boolean
|
|
502
|
+
missingImpact?: 'startupBlocked' | 'restartBlocked'
|
|
503
|
+
runtimeDependent?: boolean
|
|
504
|
+
currentPodValue?: boolean
|
|
505
|
+
placeholder?: boolean
|
|
506
|
+
evidence?: PodEnvironmentEvidence
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export interface PodEnvironmentContainer {
|
|
510
|
+
name: string
|
|
511
|
+
role: 'container' | 'init' | 'sidecar'
|
|
512
|
+
rows: PodEnvironmentRow[]
|
|
513
|
+
truncated?: boolean
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export interface PodEnvironmentResponse {
|
|
517
|
+
containers: PodEnvironmentContainer[]
|
|
518
|
+
coverage: {
|
|
519
|
+
observedSince?: string
|
|
520
|
+
degraded?: boolean
|
|
521
|
+
degradedReason?: string
|
|
522
|
+
saturated?: boolean
|
|
523
|
+
}
|
|
524
|
+
partial?: boolean
|
|
525
|
+
truncated?: boolean
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export interface PodEnvironmentRevealResponse {
|
|
529
|
+
value: string
|
|
530
|
+
encoding: 'utf8' | 'base64'
|
|
531
|
+
}
|
|
532
|
+
|
|
465
533
|
// Resource reference (for relationships)
|
|
466
534
|
export interface ResourceRef {
|
|
467
535
|
kind: string
|
|
@@ -1060,6 +1128,7 @@ export interface TopNodeMetrics {
|
|
|
1060
1128
|
name: string
|
|
1061
1129
|
cpu: number // nanocores (usage)
|
|
1062
1130
|
memory: number // bytes (usage)
|
|
1131
|
+
observedAt?: string // exact metrics sample time; absent when no sample exists
|
|
1063
1132
|
podCount: number // pods scheduled on this node
|
|
1064
1133
|
cpuAllocatable: number // nanocores
|
|
1065
1134
|
memoryAllocatable: number // bytes
|
|
@@ -1231,11 +1300,11 @@ export interface TrafficFilters {
|
|
|
1231
1300
|
timeRange: string
|
|
1232
1301
|
}
|
|
1233
1302
|
|
|
1234
|
-
// Main view type now includes 'traffic', 'cost', 'checks', 'gitops'.
|
|
1303
|
+
// Main view type now includes 'traffic', 'cost', 'capacity', 'checks', 'gitops'.
|
|
1235
1304
|
// Library consumers (Radar Hub) get all GitOps surfaces — the package
|
|
1236
1305
|
// IS the public surface, so adding new top-level views must extend
|
|
1237
1306
|
// this type rather than rely on app-local extensions.
|
|
1238
|
-
export type ExtendedMainView = MainView | 'traffic' | 'cost' | 'checks' | 'gitops' | 'issues' | 'applications'
|
|
1307
|
+
export type ExtendedMainView = MainView | 'traffic' | 'cost' | 'capacity' | 'checks' | 'gitops' | 'issues' | 'applications'
|
|
1239
1308
|
|
|
1240
1309
|
// ============================================================================
|
|
1241
1310
|
// Image Filesystem Types
|
|
@@ -1302,6 +1371,7 @@ export interface WorkloadPodInfo {
|
|
|
1302
1371
|
containers: string[]
|
|
1303
1372
|
ready: boolean
|
|
1304
1373
|
phase?: string
|
|
1374
|
+
nodeName?: string
|
|
1305
1375
|
healthLevel?: HealthStatus
|
|
1306
1376
|
reason?: string
|
|
1307
1377
|
message?: string
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { formatCPUString, formatMemoryString, parseMemoryToBytes } from './format'
|
|
3
|
+
|
|
4
|
+
describe('formatCPUString', () => {
|
|
5
|
+
it('renders exact zero as zero, not a fabricated near-zero', () => {
|
|
6
|
+
expect(formatCPUString('0')).toBe('0 cores')
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('preserves the sign of negative quantities (over-limit headroom)', () => {
|
|
10
|
+
expect(formatCPUString('-2')).toBe('-2 cores')
|
|
11
|
+
expect(formatCPUString('-500m')).toBe('-0.50 cores')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('keeps sub-centicore positives as an honest bound', () => {
|
|
15
|
+
expect(formatCPUString('5m')).toBe('<0.01 cores')
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('formatMemoryString', () => {
|
|
20
|
+
it('preserves the sign of negative quantities', () => {
|
|
21
|
+
expect(formatMemoryString('-4Gi')).toBe('-4.00 GiB')
|
|
22
|
+
expect(formatMemoryString('-256Mi')).toBe('-256 MiB')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('renders zero as zero', () => {
|
|
26
|
+
expect(formatMemoryString('0')).toBe('0 B')
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('parseMemoryToBytes', () => {
|
|
31
|
+
it('parses signed quantities instead of silently zeroing them', () => {
|
|
32
|
+
expect(parseMemoryToBytes('-1Ki')).toBe(-1024)
|
|
33
|
+
expect(parseMemoryToBytes('1Ki')).toBe(1024)
|
|
34
|
+
})
|
|
35
|
+
})
|
package/src/utils/format.ts
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* - < 1 core: 2 decimal places (e.g., "0.05 cores")
|
|
14
14
|
*/
|
|
15
15
|
function formatCoresValue(cores: number): string {
|
|
16
|
+
// Zero and negatives are real values (a pool over its limit has negative
|
|
17
|
+
// headroom) — collapsing them into "<0.01 cores" fabricates a near-zero.
|
|
18
|
+
if (cores === 0) return '0 cores'
|
|
19
|
+
if (cores < 0) return `-${formatCoresValue(-cores)}`
|
|
16
20
|
if (cores >= 1) {
|
|
17
21
|
// Round to 1 decimal
|
|
18
22
|
const rounded = Math.round(cores * 10) / 10
|
|
@@ -91,6 +95,7 @@ export function formatCPUString(cpuString: string): string {
|
|
|
91
95
|
* @returns Formatted string like "1.5 GiB" or "256 MiB"
|
|
92
96
|
*/
|
|
93
97
|
export function formatMemoryBytes(bytes: number): string {
|
|
98
|
+
if (bytes < 0) return `-${formatMemoryBytes(-bytes)}`
|
|
94
99
|
if (bytes >= 1024 * 1024 * 1024) {
|
|
95
100
|
const gib = bytes / (1024 * 1024 * 1024)
|
|
96
101
|
return gib >= 10 ? `${gib.toFixed(1)} GiB` : `${gib.toFixed(2)} GiB`
|
|
@@ -115,7 +120,7 @@ export function parseMemoryToBytes(memString: string): number {
|
|
|
115
120
|
if (!memString) return 0
|
|
116
121
|
|
|
117
122
|
const str = memString.trim()
|
|
118
|
-
const match = str.match(/^(
|
|
123
|
+
const match = str.match(/^(-?\d+(?:\.\d+)?)\s*([A-Za-z]*)$/)
|
|
119
124
|
if (!match) return 0
|
|
120
125
|
|
|
121
126
|
const num = parseFloat(match[1])
|
|
@@ -189,6 +189,15 @@ describe('refToSelectedResource', () => {
|
|
|
189
189
|
group: 'cert-manager.io',
|
|
190
190
|
})
|
|
191
191
|
})
|
|
192
|
+
|
|
193
|
+
test('normalizes an omitted namespace for cluster-scoped references', () => {
|
|
194
|
+
expect(refToSelectedResource({ kind: 'NodePool', name: 'spot' })).toEqual({
|
|
195
|
+
kind: 'nodepools',
|
|
196
|
+
name: 'spot',
|
|
197
|
+
namespace: '',
|
|
198
|
+
group: undefined,
|
|
199
|
+
})
|
|
200
|
+
})
|
|
192
201
|
})
|
|
193
202
|
|
|
194
203
|
describe('lane identity helpers', () => {
|
package/src/utils/navigation.ts
CHANGED
|
@@ -140,10 +140,12 @@ export function pluralToKind(plural: string): string {
|
|
|
140
140
|
* Convert a ResourceRef (from backend relationships) to a SelectedResource (for navigation).
|
|
141
141
|
* Handles kind singular→plural conversion.
|
|
142
142
|
*/
|
|
143
|
-
export function refToSelectedResource(
|
|
143
|
+
export function refToSelectedResource(
|
|
144
|
+
ref: Pick<ResourceRef, 'kind' | 'name' | 'group'> & { namespace?: string },
|
|
145
|
+
): SelectedResource {
|
|
144
146
|
return {
|
|
145
147
|
kind: kindToPlural(ref.kind),
|
|
146
|
-
namespace: ref.namespace,
|
|
148
|
+
namespace: ref.namespace ?? '',
|
|
147
149
|
name: ref.name,
|
|
148
150
|
group: ref.group,
|
|
149
151
|
}
|