@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/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
@@ -4,3 +4,4 @@ export * from './gitops'
4
4
  export * from './gitops-tree'
5
5
  export * from './gitops-insights'
6
6
  export * from './rbac'
7
+ export * from './capacity'
@@ -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
+ })
@@ -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(/^(\d+(?:\.\d+)?)\s*([A-Za-z]*)$/)
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', () => {
@@ -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(ref: ResourceRef): SelectedResource {
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
  }