@skyhook-io/k8s-ui 1.13.0 → 1.13.4
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 +2 -2
- package/src/components/applications/AppTooltips.tsx +13 -1
- package/src/components/applications/ApplicationDetail.test.tsx +25 -0
- package/src/components/applications/ApplicationDetail.tsx +41 -9
- package/src/components/applications/ApplicationsView.tsx +18 -3
- package/src/components/applications/ReadyBar.tsx +3 -2
- package/src/components/applications/applications-view.test.tsx +91 -0
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +35 -7
- package/src/components/issues/IssuesView.tsx +40 -31
- package/src/components/issues/ResourceIssuesSection.tsx +3 -2
- package/src/components/issues/index.ts +3 -1
- package/src/components/issues/issue-timing.ts +123 -4
- package/src/components/issues/issues.test.ts +221 -10
- package/src/components/issues/types.ts +31 -5
- package/src/components/namespace-switcher/NamespacePicker.tsx +21 -13
- package/src/components/resources/ResourcesView.tsx +973 -100
- package/src/components/resources/column-settings-key.test.ts +98 -0
- package/src/components/resources/column-visibility-merge.test.ts +64 -0
- package/src/components/resources/curated-column-ownership.test.ts +236 -0
- package/src/components/resources/gateway-policy-status.test.ts +291 -0
- package/src/components/resources/gateway-policy-status.ts +237 -0
- package/src/components/resources/generic-status.test.ts +199 -0
- package/src/components/resources/generic-status.ts +171 -0
- package/src/components/resources/gpu-ecosystem-registry.test.ts +63 -0
- package/src/components/resources/index.ts +9 -1
- package/src/components/resources/istio-gateway-filter.test.ts +97 -0
- package/src/components/resources/printer-columns.test.ts +298 -0
- package/src/components/resources/printer-columns.ts +249 -0
- package/src/components/resources/renderers/EventRenderer.test.tsx +50 -1
- package/src/components/resources/renderers/EventRenderer.tsx +3 -5
- package/src/components/resources/renderers/GenericRenderer.tsx +18 -83
- package/src/components/resources/renderers/HPARenderer.test.tsx +28 -0
- package/src/components/resources/renderers/HPARenderer.tsx +6 -1
- package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +2 -1
- package/src/components/resources/renderers/NamespaceRenderer.test.ts +30 -0
- package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -3
- package/src/components/resources/renderers/PVCRenderer.test.tsx +44 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +19 -2
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +35 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +22 -3
- package/src/components/resources/renderers/WorkloadRenderer.test.tsx +83 -0
- package/src/components/resources/renderers/WorkloadRenderer.tsx +25 -65
- package/src/components/resources/renderers/amd-gpu-cells.tsx +36 -0
- package/src/components/resources/renderers/inference-gateway-cells.tsx +62 -0
- package/src/components/resources/renderers/jobset-lws-cells.tsx +82 -0
- package/src/components/resources/renderers/kai-cells.tsx +67 -0
- package/src/components/resources/renderers/kaito-cells.tsx +62 -0
- package/src/components/resources/renderers/kserve-cells.tsx +145 -0
- package/src/components/resources/renderers/kubeflow-training-cells.tsx +72 -0
- package/src/components/resources/renderers/kueue-cells.tsx +176 -0
- package/src/components/resources/renderers/nim-cells.tsx +81 -0
- package/src/components/resources/renderers/ray-cells.tsx +124 -0
- package/src/components/resources/renderers/volcano-cells.tsx +140 -0
- package/src/components/resources/resource-utils-amd-gpu.ts +61 -0
- package/src/components/resources/resource-utils-gpu-ecosystem.test.ts +222 -0
- package/src/components/resources/resource-utils-hpa.test.ts +4 -0
- package/src/components/resources/resource-utils-hpa.ts +6 -2
- package/src/components/resources/resource-utils-inference-gateway.ts +119 -0
- package/src/components/resources/resource-utils-jobset-lws.ts +117 -0
- package/src/components/resources/resource-utils-kai.ts +105 -0
- package/src/components/resources/resource-utils-kaito.ts +114 -0
- package/src/components/resources/resource-utils-kserve.ts +186 -0
- package/src/components/resources/resource-utils-kubeflow-training.ts +121 -0
- package/src/components/resources/resource-utils-kueue.ts +198 -0
- package/src/components/resources/resource-utils-nim.ts +109 -0
- package/src/components/resources/resource-utils-ray.ts +214 -0
- package/src/components/resources/resource-utils-rollout.test.ts +183 -0
- package/src/components/resources/resource-utils-velero.test.ts +30 -0
- package/src/components/resources/resource-utils-volcano.ts +176 -0
- package/src/components/resources/resource-utils.ts +156 -47
- package/src/components/resources/resources-column-filter.test.ts +27 -1
- package/src/components/resources/workload-rollout-golden.test.ts +55 -0
- package/src/components/shared/CreateResourceDialog.tsx +1 -0
- package/src/components/shared/EditableYamlView.tsx +4 -2
- package/src/components/shared/ResourceActionsBar.tsx +170 -25
- package/src/components/shared/ResourceRendererDispatch.test.tsx +206 -1
- package/src/components/shared/ResourceRendererDispatch.tsx +84 -40
- package/src/components/shared/SetImageDialog.test.ts +129 -0
- package/src/components/shared/SetImageDialog.tsx +577 -0
- package/src/components/shared/index.ts +1 -0
- package/src/components/shared/revision-role-badges.test.ts +56 -4
- package/src/components/timeline/TimelineList.tsx +19 -3
- package/src/components/timeline/TimelineSwimlanes.tsx +11 -3
- package/src/components/trace/ReachabilityView.tsx +1 -1
- package/src/components/trace/TracePanel.tsx +1 -0
- package/src/components/ui/InClusterConsentDialog.tsx +5 -4
- package/src/components/ui/SelectMenu.tsx +202 -28
- package/src/components/ui/drawer-components.tsx +12 -0
- package/src/components/workload/WorkloadRolloutNotice.tsx +60 -0
- package/src/components/workload/WorkloadView.tsx +146 -83
- package/src/hooks/useProgressiveRefresh.ts +37 -0
- package/src/source-hygiene.test.ts +28 -0
- package/src/theme/tailwind-theme.css +4 -0
- package/src/types/core.ts +62 -4
- package/src/utils/api-resources.test.ts +40 -1
- package/src/utils/api-resources.ts +11 -1
- package/src/utils/applications.ts +50 -0
- package/src/utils/bulk-workload-actions.test.ts +20 -0
- package/src/utils/bulk-workload-actions.ts +15 -0
- 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 +31 -1
- package/src/utils/navigation.ts +21 -2
- package/src/utils/workload-rollout.ts +292 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// KAITO CRD utility functions
|
|
2
|
+
|
|
3
|
+
import type { StatusBadge } from './resource-utils'
|
|
4
|
+
import { healthColors } from './resource-utils'
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// KAITO WORKSPACE UTILITIES
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
// Workspace has top-level resource/inference/tuning fields, not under spec
|
|
11
|
+
export function getKaitoWorkspaceStatus(resource: any): StatusBadge {
|
|
12
|
+
const state = resource.status?.state
|
|
13
|
+
if (state === 'Failed') {
|
|
14
|
+
return { text: 'Failed', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
15
|
+
}
|
|
16
|
+
if (state === 'NotReady') {
|
|
17
|
+
return { text: 'NotReady', color: healthColors.degraded, level: 'degraded' }
|
|
18
|
+
}
|
|
19
|
+
if (state === 'Ready' || state === 'Running') {
|
|
20
|
+
return { text: state, color: healthColors.healthy, level: 'healthy' }
|
|
21
|
+
}
|
|
22
|
+
if (state === 'Succeeded') {
|
|
23
|
+
return { text: 'Succeeded', color: healthColors.neutral, level: 'neutral' }
|
|
24
|
+
}
|
|
25
|
+
if (state === 'Pending') {
|
|
26
|
+
return { text: 'Pending', color: healthColors.neutral, level: 'neutral' }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const conditions = resource.status?.conditions || []
|
|
30
|
+
if (conditions.length === 0) {
|
|
31
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const find = (type: string) => conditions.find((c: any) => c.type === type)
|
|
35
|
+
const succeeded = find('WorkspaceSucceeded')
|
|
36
|
+
const resourceReady = find('ResourceReady')
|
|
37
|
+
const inferenceReady = find('InferenceReady')
|
|
38
|
+
const jobStarted = find('JobStarted')
|
|
39
|
+
|
|
40
|
+
if (succeeded?.status === 'True') {
|
|
41
|
+
return { text: 'Succeeded', color: healthColors.neutral, level: 'neutral' }
|
|
42
|
+
}
|
|
43
|
+
if (resourceReady?.status === 'False') {
|
|
44
|
+
return { text: resourceReady.reason || 'ResourcesNotReady', color: healthColors.alert, level: 'alert' }
|
|
45
|
+
}
|
|
46
|
+
if (inferenceReady?.status === 'False') {
|
|
47
|
+
return { text: inferenceReady.reason || 'InferenceNotReady', color: healthColors.degraded, level: 'degraded' }
|
|
48
|
+
}
|
|
49
|
+
if (succeeded?.status === 'False') {
|
|
50
|
+
return { text: succeeded.reason || 'NotReady', color: healthColors.degraded, level: 'degraded' }
|
|
51
|
+
}
|
|
52
|
+
if (resourceReady?.status === 'True' || inferenceReady?.status === 'True' || jobStarted?.status === 'True') {
|
|
53
|
+
return { text: 'Progressing', color: healthColors.degraded, level: 'degraded' }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getKaitoWorkspaceInstanceType(resource: any): string {
|
|
60
|
+
return resource.resource?.instanceType || '-'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getKaitoWorkspacePreset(resource: any): string {
|
|
64
|
+
return resource.inference?.preset?.name || resource.tuning?.preset?.name || '-'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getKaitoWorkspaceNodeCount(resource: any): string {
|
|
68
|
+
const actual = (resource.status?.workerNodes || []).length
|
|
69
|
+
const desired = resource.status?.targetNodeCount ?? resource.resource?.count
|
|
70
|
+
if (desired !== undefined && desired !== null) return `${actual}/${desired}`
|
|
71
|
+
return `${actual}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// KAITO RAGENGINE UTILITIES
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
export function getRAGEngineStatus(resource: any): StatusBadge {
|
|
79
|
+
const conditions = resource.status?.conditions || []
|
|
80
|
+
if (conditions.length === 0) {
|
|
81
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const find = (type: string) => conditions.find((c: any) => c.type === type)
|
|
85
|
+
const resourceReady = find('ResourceReady')
|
|
86
|
+
const serviceReady = find('ServiceReady')
|
|
87
|
+
|
|
88
|
+
if (resourceReady?.status === 'False') {
|
|
89
|
+
return { text: resourceReady.reason || 'ResourcesNotReady', color: healthColors.alert, level: 'alert' }
|
|
90
|
+
}
|
|
91
|
+
if (serviceReady?.status === 'False') {
|
|
92
|
+
return { text: serviceReady.reason || 'ServiceNotReady', color: healthColors.degraded, level: 'degraded' }
|
|
93
|
+
}
|
|
94
|
+
if (resourceReady?.status === 'True' && serviceReady?.status === 'True') {
|
|
95
|
+
return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
|
|
96
|
+
}
|
|
97
|
+
if (resourceReady?.status === 'True' || serviceReady?.status === 'True') {
|
|
98
|
+
return { text: 'Progressing', color: healthColors.degraded, level: 'degraded' }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getRAGEngineEmbeddingModel(resource: any): string {
|
|
105
|
+
const embedding = resource.spec?.embedding
|
|
106
|
+
if (!embedding) return '-'
|
|
107
|
+
if (embedding.local) return embedding.local.modelID || embedding.local.image || '-'
|
|
108
|
+
if (embedding.remote) return embedding.remote.url || 'remote'
|
|
109
|
+
return '-'
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getRAGEngineInstanceType(resource: any): string {
|
|
113
|
+
return resource.spec?.compute?.instanceType || '-'
|
|
114
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// KServe CRD utility functions
|
|
2
|
+
|
|
3
|
+
import type { StatusBadge } from './resource-utils'
|
|
4
|
+
import { healthColors } from './resource-utils'
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// SHARED HELPERS
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
function findCondition(resource: any, type: string): any {
|
|
11
|
+
const conditions = resource.status?.conditions
|
|
12
|
+
if (!Array.isArray(conditions)) return undefined
|
|
13
|
+
return conditions.find((c: any) => c?.type === type)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function truncateMiddle(value: string, max = 44): string {
|
|
17
|
+
if (value.length <= max) return value
|
|
18
|
+
const keep = Math.floor((max - 1) / 2)
|
|
19
|
+
return `${value.slice(0, keep)}…${value.slice(-keep)}`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// KSERVE INFERENCESERVICE UTILITIES
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
export function getInferenceServiceStatus(resource: any): StatusBadge {
|
|
27
|
+
const transition = resource.status?.modelStatus?.transitionStatus
|
|
28
|
+
if (transition === 'BlockedByFailedLoad') {
|
|
29
|
+
return { text: 'Load failed', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
30
|
+
}
|
|
31
|
+
if (transition === 'InvalidSpec') {
|
|
32
|
+
return { text: 'Invalid spec', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const readyCond = findCondition(resource, 'Ready')
|
|
36
|
+
if (readyCond?.status === 'True') {
|
|
37
|
+
return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
|
|
38
|
+
}
|
|
39
|
+
if (readyCond) {
|
|
40
|
+
return { text: readyCond.reason || 'NotReady', color: healthColors.degraded, level: 'degraded' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getInferenceServiceUrl(resource: any): string {
|
|
47
|
+
const url = resource.status?.url
|
|
48
|
+
return typeof url === 'string' && url ? url : '-'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getInferenceServiceModelFormat(resource: any): string {
|
|
52
|
+
return resource.spec?.predictor?.model?.modelFormat?.name || '-'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getInferenceServiceRuntime(resource: any): string {
|
|
56
|
+
return resource.spec?.predictor?.model?.runtime || '-'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getInferenceServiceDeploymentMode(resource: any): string {
|
|
60
|
+
const annotations = resource.metadata?.annotations || {}
|
|
61
|
+
return annotations['serving.kserve.io/deploymentMode'] || resource.status?.deploymentMode || '-'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// KSERVE SERVINGRUNTIME / CLUSTERSERVINGRUNTIME UTILITIES
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
export function getServingRuntimeStatus(resource: any): StatusBadge {
|
|
69
|
+
if (resource.spec?.disabled === true) {
|
|
70
|
+
return { text: 'Disabled', color: healthColors.neutral, level: 'neutral' }
|
|
71
|
+
}
|
|
72
|
+
return { text: 'Enabled', color: healthColors.neutral, level: 'neutral' }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getServingRuntimeModelFormats(resource: any): string {
|
|
76
|
+
const formats = resource.spec?.supportedModelFormats
|
|
77
|
+
if (!Array.isArray(formats) || formats.length === 0) return '-'
|
|
78
|
+
const names = [...new Set(formats.map((f: any) => f?.name).filter(Boolean))] as string[]
|
|
79
|
+
if (names.length === 0) return '-'
|
|
80
|
+
if (names.length > 3) return `${names.slice(0, 3).join(', ')} +${names.length - 3}`
|
|
81
|
+
return names.join(', ')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getServingRuntimeImage(resource: any): string {
|
|
85
|
+
const containers = resource.spec?.containers
|
|
86
|
+
if (!Array.isArray(containers) || containers.length === 0) return '-'
|
|
87
|
+
return containers[0]?.image || '-'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// KSERVE INFERENCEGRAPH UTILITIES
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
export function getInferenceGraphStatus(resource: any): StatusBadge {
|
|
95
|
+
const readyCond = findCondition(resource, 'Ready')
|
|
96
|
+
if (readyCond?.status === 'True') {
|
|
97
|
+
return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
|
|
98
|
+
}
|
|
99
|
+
if (readyCond?.status === 'False') {
|
|
100
|
+
return { text: readyCond.reason || 'NotReady', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
101
|
+
}
|
|
102
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getInferenceGraphNodeCount(resource: any): number {
|
|
106
|
+
const nodes = resource.spec?.nodes
|
|
107
|
+
if (!nodes || typeof nodes !== 'object' || Array.isArray(nodes)) return 0
|
|
108
|
+
return Object.keys(nodes).length
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// KSERVE TRAINEDMODEL UTILITIES
|
|
113
|
+
// ============================================================================
|
|
114
|
+
|
|
115
|
+
export function getTrainedModelStatus(resource: any): StatusBadge {
|
|
116
|
+
const readyCond = findCondition(resource, 'Ready')
|
|
117
|
+
if (readyCond?.status === 'True') {
|
|
118
|
+
return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
|
|
119
|
+
}
|
|
120
|
+
if (readyCond?.status === 'False') {
|
|
121
|
+
return { text: readyCond.reason || 'NotReady', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
122
|
+
}
|
|
123
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function getTrainedModelFramework(resource: any): string {
|
|
127
|
+
return resource.spec?.model?.framework || '-'
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getTrainedModelStorageUri(resource: any): string {
|
|
131
|
+
const uri = resource.spec?.model?.storageUri
|
|
132
|
+
if (typeof uri !== 'string' || !uri) return '-'
|
|
133
|
+
return truncateMiddle(uri)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getTrainedModelInferenceService(resource: any): string {
|
|
137
|
+
return resource.spec?.inferenceService || '-'
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// KSERVE LLMINFERENCESERVICE UTILITIES
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
export function getLLMInferenceServiceStatus(resource: any): StatusBadge {
|
|
145
|
+
const conditions = Array.isArray(resource.status?.conditions) ? resource.status.conditions : []
|
|
146
|
+
|
|
147
|
+
const readyCond = conditions.find((c: any) => c?.type === 'Ready')
|
|
148
|
+
if (readyCond?.status === 'True') {
|
|
149
|
+
return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
|
|
150
|
+
}
|
|
151
|
+
if (readyCond?.status === 'False') {
|
|
152
|
+
return { text: readyCond.reason || 'NotReady', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (conditions.length > 0) {
|
|
156
|
+
const failing = conditions.find((c: any) => c?.status === 'False')
|
|
157
|
+
if (failing) {
|
|
158
|
+
return { text: failing.reason || failing.type || 'NotReady', color: healthColors.degraded, level: 'degraded' }
|
|
159
|
+
}
|
|
160
|
+
if (conditions.every((c: any) => c?.status === 'True')) {
|
|
161
|
+
return { text: 'Ready', color: healthColors.healthy, level: 'healthy' }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getLLMInferenceServiceModel(resource: any): string {
|
|
169
|
+
const model = resource.spec?.model
|
|
170
|
+
if (!model || typeof model !== 'object') return '-'
|
|
171
|
+
if (model.name) return model.name
|
|
172
|
+
if (typeof model.uri === 'string' && model.uri) return truncateMiddle(model.uri)
|
|
173
|
+
return '-'
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function getLLMInferenceServiceModelUri(resource: any): string {
|
|
177
|
+
const uri = resource.spec?.model?.uri
|
|
178
|
+
if (typeof uri !== 'string' || !uri) return '-'
|
|
179
|
+
return truncateMiddle(uri)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function getLLMInferenceServiceReplicas(resource: any): string {
|
|
183
|
+
const replicas = resource.spec?.replicas
|
|
184
|
+
if (replicas === undefined || replicas === null) return '-'
|
|
185
|
+
return String(replicas)
|
|
186
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Kubeflow training CRD utility functions
|
|
2
|
+
// Covers training-operator v1 (PyTorchJob/TFJob/MPIJob at kubeflow.org/v1),
|
|
3
|
+
// mpi-operator (MPIJob at kubeflow.org/v2beta1), and Trainer v2 (TrainJob at trainer.kubeflow.org/v1alpha1)
|
|
4
|
+
|
|
5
|
+
import type { StatusBadge } from './resource-utils'
|
|
6
|
+
import { healthColors, formatDuration } from './resource-utils'
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// SHARED HELPERS (kubeflow.org JobCondition pattern)
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
const REPLICA_SPEC_KEYS = ['pytorchReplicaSpecs', 'tfReplicaSpecs', 'mpiReplicaSpecs']
|
|
13
|
+
|
|
14
|
+
function getReplicaSpecs(resource: any): Record<string, any> {
|
|
15
|
+
const spec = resource.spec || {}
|
|
16
|
+
for (const key of REPLICA_SPEC_KEYS) {
|
|
17
|
+
if (spec[key]) return spec[key]
|
|
18
|
+
}
|
|
19
|
+
return {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function trainingJobStatus(resource: any): StatusBadge {
|
|
23
|
+
const conditions = resource.status?.conditions || []
|
|
24
|
+
const isTrue = (type: string) => conditions.some((condition: any) => condition?.type === type && condition?.status === 'True')
|
|
25
|
+
if (isTrue('Failed')) {
|
|
26
|
+
return { text: 'Failed', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
27
|
+
}
|
|
28
|
+
if (isTrue('Succeeded')) {
|
|
29
|
+
return { text: 'Succeeded', color: healthColors.neutral, level: 'neutral' }
|
|
30
|
+
}
|
|
31
|
+
if (isTrue('Suspended')) {
|
|
32
|
+
return { text: 'Suspended', color: healthColors.degraded, level: 'degraded' }
|
|
33
|
+
}
|
|
34
|
+
if (isTrue('Restarting')) {
|
|
35
|
+
return { text: 'Restarting', color: healthColors.degraded, level: 'degraded' }
|
|
36
|
+
}
|
|
37
|
+
if (isTrue('Running')) {
|
|
38
|
+
return { text: 'Running', color: healthColors.healthy, level: 'healthy' }
|
|
39
|
+
}
|
|
40
|
+
return { text: 'Pending', color: healthColors.neutral, level: 'neutral' }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getTrainingJobReplicas(resource: any): string {
|
|
44
|
+
const specs = getReplicaSpecs(resource)
|
|
45
|
+
const statuses = resource.status?.replicaStatuses || {}
|
|
46
|
+
const types = Object.keys(specs)
|
|
47
|
+
for (const type of Object.keys(statuses)) {
|
|
48
|
+
if (!types.includes(type)) types.push(type)
|
|
49
|
+
}
|
|
50
|
+
if (types.length === 0) return '-'
|
|
51
|
+
return types
|
|
52
|
+
.map((type) => {
|
|
53
|
+
const status = statuses[type] || {}
|
|
54
|
+
const progress = (status.active ?? 0) + (status.succeeded ?? 0)
|
|
55
|
+
const failed = status.failed ?? 0
|
|
56
|
+
const desired = specs[type]?.replicas
|
|
57
|
+
// Unset spec replicas are defaulted in-controller (and the default
|
|
58
|
+
// differs per operator and replica type), so omit the denominator
|
|
59
|
+
// rather than guess
|
|
60
|
+
const count = desired == null ? `${type} ${progress}` : `${type} ${progress}/${desired}`
|
|
61
|
+
return failed > 0 ? `${count} (${failed} failed)` : count
|
|
62
|
+
})
|
|
63
|
+
.join(', ')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getTrainingJobElapsed(resource: any): string {
|
|
67
|
+
const startTime = resource.status?.startTime
|
|
68
|
+
if (!startTime) return '-'
|
|
69
|
+
const start = new Date(startTime)
|
|
70
|
+
const completionTime = resource.status?.completionTime
|
|
71
|
+
const end = completionTime ? new Date(completionTime) : new Date()
|
|
72
|
+
return formatDuration(end.getTime() - start.getTime())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// PYTORCHJOB / TFJOB / MPIJOB STATUS
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
export function getPyTorchJobStatus(resource: any): StatusBadge {
|
|
80
|
+
return trainingJobStatus(resource)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getTFJobStatus(resource: any): StatusBadge {
|
|
84
|
+
return trainingJobStatus(resource)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function getMPIJobStatus(resource: any): StatusBadge {
|
|
88
|
+
return trainingJobStatus(resource)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// TRAINJOB (trainer.kubeflow.org/v1alpha1)
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
export function getTrainJobStatus(resource: any): StatusBadge {
|
|
96
|
+
const conditions = resource.status?.conditions || []
|
|
97
|
+
const isTrue = (type: string) => conditions.some((c: any) => c?.type === type && c?.status === 'True')
|
|
98
|
+
|
|
99
|
+
if (isTrue('Failed')) {
|
|
100
|
+
return { text: 'Failed', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
101
|
+
}
|
|
102
|
+
if (isTrue('Complete')) {
|
|
103
|
+
return { text: 'Complete', color: healthColors.neutral, level: 'neutral' }
|
|
104
|
+
}
|
|
105
|
+
if (isTrue('Suspended') || resource.spec?.suspend === true) {
|
|
106
|
+
return { text: 'Suspended', color: healthColors.degraded, level: 'degraded' }
|
|
107
|
+
}
|
|
108
|
+
const jobsStatus = Array.isArray(resource.status?.jobsStatus) ? resource.status.jobsStatus : []
|
|
109
|
+
if (jobsStatus.some((job: any) => Number(job?.active) > 0)) {
|
|
110
|
+
return { text: 'Running', color: healthColors.healthy, level: 'healthy' }
|
|
111
|
+
}
|
|
112
|
+
return { text: 'Pending', color: healthColors.neutral, level: 'neutral' }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function getTrainJobRuntime(resource: any): string {
|
|
116
|
+
return resource.spec?.runtimeRef?.name || '-'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function getTrainJobSuspended(resource: any): boolean {
|
|
120
|
+
return resource.spec?.suspend === true
|
|
121
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Kueue + Cluster Autoscaler ProvisioningRequest CRD utility functions
|
|
2
|
+
|
|
3
|
+
import type { StatusBadge } from './resource-utils'
|
|
4
|
+
import { healthColors } from './resource-utils'
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// SHARED HELPERS
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
function findCondition(resource: any, type: string): any {
|
|
11
|
+
return (resource?.status?.conditions || []).find((c: any) => c?.type === type)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function activeConditionStatus(resource: any): StatusBadge {
|
|
15
|
+
const active = findCondition(resource, 'Active')
|
|
16
|
+
if (active?.status === 'True') {
|
|
17
|
+
return { text: 'Active', color: healthColors.healthy, level: 'healthy' }
|
|
18
|
+
}
|
|
19
|
+
if (active?.status === 'False') {
|
|
20
|
+
return { text: active.reason || 'Inactive', color: healthColors.alert, level: 'alert' }
|
|
21
|
+
}
|
|
22
|
+
return { text: 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatWorkloadCount(value: any): string {
|
|
26
|
+
return typeof value === 'number' ? String(value) : '-'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// KUEUE CLUSTERQUEUE UTILITIES
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
export function getClusterQueueStatus(resource: any): StatusBadge {
|
|
34
|
+
return activeConditionStatus(resource)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getClusterQueueCohort(resource: any): string {
|
|
38
|
+
// v1beta2 renamed spec.cohort to spec.cohortName
|
|
39
|
+
return resource?.spec?.cohortName || resource?.spec?.cohort || '-'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getClusterQueuePendingWorkloads(resource: any): string {
|
|
43
|
+
return formatWorkloadCount(resource?.status?.pendingWorkloads)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getClusterQueueAdmittedWorkloads(resource: any): string {
|
|
47
|
+
return formatWorkloadCount(resource?.status?.admittedWorkloads)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getClusterQueueFlavors(resource: any): string {
|
|
51
|
+
const groups = resource?.spec?.resourceGroups || []
|
|
52
|
+
const flavors = [
|
|
53
|
+
...new Set(groups.flatMap((g: any) => (g?.flavors || []).map((f: any) => f?.name).filter(Boolean))),
|
|
54
|
+
] as string[]
|
|
55
|
+
if (flavors.length === 0) return '-'
|
|
56
|
+
if (flavors.length > 3) return `${flavors.slice(0, 3).join(', ')} +${flavors.length - 3}`
|
|
57
|
+
return flavors.join(', ')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// KUEUE LOCALQUEUE UTILITIES
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
export function getLocalQueueStatus(resource: any): StatusBadge {
|
|
65
|
+
return activeConditionStatus(resource)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getLocalQueueClusterQueue(resource: any): string {
|
|
69
|
+
return resource?.spec?.clusterQueue || '-'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getLocalQueuePendingWorkloads(resource: any): string {
|
|
73
|
+
return formatWorkloadCount(resource?.status?.pendingWorkloads)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getLocalQueueAdmittedWorkloads(resource: any): string {
|
|
77
|
+
return formatWorkloadCount(resource?.status?.admittedWorkloads)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// KUEUE WORKLOAD UTILITIES
|
|
82
|
+
// ============================================================================
|
|
83
|
+
|
|
84
|
+
export function getKueueWorkloadStatus(resource: any): StatusBadge {
|
|
85
|
+
const finished = findCondition(resource, 'Finished')
|
|
86
|
+
if (finished?.status === 'True') {
|
|
87
|
+
if (finished.reason === 'Failed') {
|
|
88
|
+
return { text: 'Failed', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
89
|
+
}
|
|
90
|
+
return { text: 'Finished', color: healthColors.neutral, level: 'neutral' }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const evicted = findCondition(resource, 'Evicted')
|
|
94
|
+
if (evicted?.status === 'True') {
|
|
95
|
+
return { text: 'Evicted', color: healthColors.degraded, level: 'degraded' }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const preempted = findCondition(resource, 'Preempted')
|
|
99
|
+
if (preempted?.status === 'True') {
|
|
100
|
+
return { text: 'Preempted', color: healthColors.degraded, level: 'degraded' }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const admitted = findCondition(resource, 'Admitted')
|
|
104
|
+
if (admitted?.status === 'True') {
|
|
105
|
+
return { text: 'Admitted', color: healthColors.healthy, level: 'healthy' }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const quotaReserved = findCondition(resource, 'QuotaReserved')
|
|
109
|
+
if (quotaReserved?.status === 'True') {
|
|
110
|
+
return { text: 'QuotaReserved', color: healthColors.neutral, level: 'neutral' }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { text: 'Pending', color: healthColors.neutral, level: 'neutral' }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getKueueWorkloadQueueName(resource: any): string {
|
|
117
|
+
return resource?.spec?.queueName || '-'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getKueueWorkloadAdmittedBy(resource: any): string {
|
|
121
|
+
return resource?.status?.admission?.clusterQueue || '-'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function getKueueWorkloadPriority(resource: any): string {
|
|
125
|
+
const priority = resource?.spec?.priority
|
|
126
|
+
if (typeof priority === 'number') return String(priority)
|
|
127
|
+
// v1beta1 uses spec.priorityClassName, v1beta2 uses spec.priorityClassRef
|
|
128
|
+
return resource?.spec?.priorityClassRef?.name || resource?.spec?.priorityClassName || '-'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// KUEUE RESOURCEFLAVOR UTILITIES
|
|
133
|
+
// ============================================================================
|
|
134
|
+
|
|
135
|
+
export function getResourceFlavorStatus(_resource: any): StatusBadge {
|
|
136
|
+
return { text: 'Configured', color: healthColors.neutral, level: 'neutral' }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function getResourceFlavorNodeLabelCount(resource: any): number {
|
|
140
|
+
return Object.keys(resource?.spec?.nodeLabels || {}).length
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getResourceFlavorTaintCount(resource: any): number {
|
|
144
|
+
return (resource?.spec?.nodeTaints || []).length
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================================
|
|
148
|
+
// KUEUE ADMISSIONCHECK UTILITIES
|
|
149
|
+
// ============================================================================
|
|
150
|
+
|
|
151
|
+
export function getAdmissionCheckStatus(resource: any): StatusBadge {
|
|
152
|
+
return activeConditionStatus(resource)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function getAdmissionCheckControllerName(resource: any): string {
|
|
156
|
+
return resource?.spec?.controllerName || '-'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// CLUSTER AUTOSCALER PROVISIONINGREQUEST UTILITIES
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
export function getProvisioningRequestStatus(resource: any): StatusBadge {
|
|
164
|
+
const failed = findCondition(resource, 'Failed')
|
|
165
|
+
if (failed?.status === 'True') {
|
|
166
|
+
return { text: 'Failed', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const capacityRevoked = findCondition(resource, 'CapacityRevoked')
|
|
170
|
+
if (capacityRevoked?.status === 'True') {
|
|
171
|
+
return { text: 'CapacityRevoked', color: healthColors.degraded, level: 'degraded' }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const bookingExpired = findCondition(resource, 'BookingExpired')
|
|
175
|
+
if (bookingExpired?.status === 'True') {
|
|
176
|
+
return { text: 'BookingExpired', color: healthColors.degraded, level: 'degraded' }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const provisioned = findCondition(resource, 'Provisioned')
|
|
180
|
+
if (provisioned?.status === 'True') {
|
|
181
|
+
return { text: 'Provisioned', color: healthColors.healthy, level: 'healthy' }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const accepted = findCondition(resource, 'Accepted')
|
|
185
|
+
if (accepted?.status === 'True') {
|
|
186
|
+
return { text: 'Accepted', color: healthColors.neutral, level: 'neutral' }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { text: 'Pending', color: healthColors.neutral, level: 'neutral' }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function getProvisioningRequestClassName(resource: any): string {
|
|
193
|
+
return resource?.spec?.provisioningClassName || '-'
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function getProvisioningRequestPodSetCount(resource: any): number {
|
|
197
|
+
return (resource?.spec?.podSets || []).length
|
|
198
|
+
}
|