@skyhook-io/k8s-ui 1.13.3 → 1.14.0
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 +4 -4
- package/src/components/applications/AppTooltips.tsx +13 -1
- package/src/components/applications/ApplicationDetail.test.tsx +25 -0
- package/src/components/applications/ApplicationDetail.tsx +39 -7
- 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 +4 -1
- 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 +966 -98
- 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/kyverno-legacy-action.test.ts +100 -1
- package/src/components/resources/kyverno-modern-posture.test.ts +36 -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/CNPGBackupRenderer.test.tsx +29 -0
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +3 -2
- package/src/components/resources/renderers/CNPGClusterRenderer.test.tsx +69 -3
- package/src/components/resources/renderers/CNPGClusterRenderer.tsx +6 -2
- 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/KyvernoPolicyReportRenderer.tsx +12 -7
- package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +2 -1
- package/src/components/resources/renderers/PVCRenderer.test.tsx +44 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +19 -2
- package/src/components/resources/renderers/PolicyCoverageSection.test.tsx +92 -1
- package/src/components/resources/renderers/PolicyCoverageSection.tsx +64 -8
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +35 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +22 -3
- package/src/components/resources/renderers/VeleroBSLRenderer.test.tsx +47 -0
- package/src/components/resources/renderers/VeleroBSLRenderer.tsx +16 -1
- package/src/components/resources/renderers/VeleroRestoreRenderer.test.tsx +88 -0
- package/src/components/resources/renderers/VeleroRestoreRenderer.tsx +44 -2
- 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/cnpg-cells.tsx +12 -1
- 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/kyverno-cells.tsx +8 -4
- 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-cnpg.golden.test.ts +13 -5
- package/src/components/resources/resource-utils-cnpg.test.ts +142 -2
- package/src/components/resources/resource-utils-cnpg.ts +132 -7
- package/src/components/resources/resource-utils-gpu-ecosystem.test.ts +236 -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 +200 -0
- package/src/components/resources/resource-utils-kyverno-modern.ts +3 -1
- package/src/components/resources/resource-utils-kyverno.ts +51 -2
- 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-velero.ts +32 -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/ui/SelectMenu.tsx +10 -1
- package/src/components/ui/drawer-components.tsx +12 -0
- package/src/components/workload/WorkloadRolloutNotice.tsx +60 -0
- package/src/components/workload/WorkloadView.tsx +144 -81
- 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 +55 -0
- 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/navigation.test.ts +31 -1
- package/src/utils/navigation.ts +21 -2
- package/src/utils/workload-rollout.ts +292 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Volcano 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 summarizeResourceList(list: any): string {
|
|
11
|
+
if (!list || typeof list !== 'object') return '-'
|
|
12
|
+
const parts: string[] = []
|
|
13
|
+
if (list.cpu !== undefined) parts.push(`CPU: ${list.cpu}`)
|
|
14
|
+
if (list.memory !== undefined) parts.push(`Mem: ${list.memory}`)
|
|
15
|
+
if (list['nvidia.com/gpu'] !== undefined) parts.push(`GPU: ${list['nvidia.com/gpu']}`)
|
|
16
|
+
const rest = Object.keys(list).filter((k) => k !== 'cpu' && k !== 'memory' && k !== 'nvidia.com/gpu')
|
|
17
|
+
if (rest.length > 0) parts.push(`+${rest.length} more`)
|
|
18
|
+
return parts.length > 0 ? parts.join(', ') : '-'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function summarizeCounts(entries: Array<[string, number]>): string {
|
|
22
|
+
const parts = entries.filter(([, count]) => count > 0).map(([label, count]) => `${count} ${label}`)
|
|
23
|
+
return parts.length > 0 ? parts.join(', ') : '-'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// VOLCANO JOB UTILITIES (batch.volcano.sh/v1alpha1)
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
export function getVolcanoJobStatus(resource: any): StatusBadge {
|
|
31
|
+
const phase = resource.status?.state?.phase
|
|
32
|
+
switch (phase) {
|
|
33
|
+
case 'Running':
|
|
34
|
+
return { text: 'Running', color: healthColors.healthy, level: 'healthy' }
|
|
35
|
+
case 'Pending':
|
|
36
|
+
case 'Completing':
|
|
37
|
+
case 'Completed':
|
|
38
|
+
return { text: phase, color: healthColors.neutral, level: 'neutral' }
|
|
39
|
+
case 'Failed':
|
|
40
|
+
case 'Aborted':
|
|
41
|
+
case 'Terminated':
|
|
42
|
+
return { text: phase, color: healthColors.unhealthy, level: 'unhealthy' }
|
|
43
|
+
case 'Aborting':
|
|
44
|
+
case 'Restarting':
|
|
45
|
+
case 'Terminating':
|
|
46
|
+
return { text: phase, color: healthColors.degraded, level: 'degraded' }
|
|
47
|
+
default:
|
|
48
|
+
return { text: phase || 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getVolcanoJobQueue(resource: any): string {
|
|
53
|
+
return resource.spec?.queue || 'default'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getVolcanoJobMinAvailable(resource: any): string {
|
|
57
|
+
const min = resource.spec?.minAvailable ?? resource.status?.minAvailable
|
|
58
|
+
return min !== undefined && min !== null ? String(min) : '-'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function getVolcanoJobPodCounts(resource: any): string {
|
|
62
|
+
const status = resource.status || {}
|
|
63
|
+
return summarizeCounts([
|
|
64
|
+
['running', status.running || 0],
|
|
65
|
+
['succeeded', status.succeeded || 0],
|
|
66
|
+
['failed', status.failed || 0],
|
|
67
|
+
])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// VOLCANO QUEUE UTILITIES (scheduling.volcano.sh/v1beta1, cluster-scoped)
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
export function getVolcanoQueueStatus(resource: any): StatusBadge {
|
|
75
|
+
const state = resource.status?.state
|
|
76
|
+
switch (state) {
|
|
77
|
+
case 'Open':
|
|
78
|
+
return { text: 'Open', color: healthColors.healthy, level: 'healthy' }
|
|
79
|
+
case 'Closed':
|
|
80
|
+
return { text: 'Closed', color: healthColors.neutral, level: 'neutral' }
|
|
81
|
+
case 'Closing':
|
|
82
|
+
return { text: 'Closing', color: healthColors.degraded, level: 'degraded' }
|
|
83
|
+
default:
|
|
84
|
+
return { text: state || 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getVolcanoQueueWeight(resource: any): string {
|
|
89
|
+
const weight = resource.spec?.weight
|
|
90
|
+
return weight !== undefined && weight !== null ? String(weight) : '1'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getVolcanoQueueCapability(resource: any): string {
|
|
94
|
+
return summarizeResourceList(resource.spec?.capability)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getVolcanoQueueAllocated(resource: any): string {
|
|
98
|
+
return summarizeResourceList(resource.status?.allocated)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getVolcanoQueuePodGroupCounts(resource: any): string {
|
|
102
|
+
const status = resource.status || {}
|
|
103
|
+
return summarizeCounts([
|
|
104
|
+
['running', status.running || 0],
|
|
105
|
+
['inqueue', status.inqueue || 0],
|
|
106
|
+
['pending', status.pending || 0],
|
|
107
|
+
['completed', status.completed || 0],
|
|
108
|
+
['unknown', status.unknown || 0],
|
|
109
|
+
])
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============================================================================
|
|
113
|
+
// VOLCANO PODGROUP UTILITIES (scheduling.volcano.sh/v1beta1)
|
|
114
|
+
// ============================================================================
|
|
115
|
+
|
|
116
|
+
export function getVolcanoPodGroupStatus(resource: any): StatusBadge {
|
|
117
|
+
const phase = resource.status?.phase
|
|
118
|
+
if (phase === 'Running') {
|
|
119
|
+
return { text: 'Running', color: healthColors.healthy, level: 'healthy' }
|
|
120
|
+
}
|
|
121
|
+
const conditions = resource.status?.conditions || []
|
|
122
|
+
const unschedulable = conditions.some((c: any) => c.type === 'Unschedulable' && c.status === 'True')
|
|
123
|
+
if (unschedulable) {
|
|
124
|
+
return { text: 'Unschedulable', color: healthColors.alert, level: 'alert' }
|
|
125
|
+
}
|
|
126
|
+
if (phase === 'Pending' || phase === 'Inqueue' || phase === 'Completed') {
|
|
127
|
+
return { text: phase, color: healthColors.neutral, level: 'neutral' }
|
|
128
|
+
}
|
|
129
|
+
return { text: phase || 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function getVolcanoPodGroupQueue(resource: any): string {
|
|
133
|
+
return resource.spec?.queue || 'default'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getVolcanoPodGroupMinMember(resource: any): string {
|
|
137
|
+
const min = resource.spec?.minMember
|
|
138
|
+
return min !== undefined && min !== null ? String(min) : '-'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// VOLCANO JOBFLOW UTILITIES (flow.volcano.sh/v1alpha1)
|
|
143
|
+
// ============================================================================
|
|
144
|
+
|
|
145
|
+
export function getJobFlowStatus(resource: any): StatusBadge {
|
|
146
|
+
const phase = resource.status?.state?.phase
|
|
147
|
+
switch (phase) {
|
|
148
|
+
case 'Running':
|
|
149
|
+
return { text: 'Running', color: healthColors.healthy, level: 'healthy' }
|
|
150
|
+
case 'Succeed':
|
|
151
|
+
case 'Pending':
|
|
152
|
+
return { text: phase, color: healthColors.neutral, level: 'neutral' }
|
|
153
|
+
case 'Failed':
|
|
154
|
+
return { text: 'Failed', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
155
|
+
case 'Terminating':
|
|
156
|
+
return { text: 'Terminating', color: healthColors.degraded, level: 'degraded' }
|
|
157
|
+
default:
|
|
158
|
+
return { text: phase || 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getJobFlowFlowCount(resource: any): number {
|
|
163
|
+
return (resource.spec?.flows || []).length
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// VOLCANO JOBTEMPLATE UTILITIES (flow.volcano.sh/v1alpha1)
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
export function getJobTemplateStatus(_resource: any): StatusBadge {
|
|
171
|
+
return { text: 'Template', color: healthColors.neutral, level: 'neutral' }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function getJobTemplateTaskCount(resource: any): number {
|
|
175
|
+
return (resource.spec?.tasks || []).length
|
|
176
|
+
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { formatCPUString, formatMemoryString, formatBytes } from '../../utils/format'
|
|
4
4
|
import { pluralize } from '../../utils/pluralize'
|
|
5
|
+
import type { WorkloadPodInfo } from '../../types/core'
|
|
6
|
+
import { getArgoRolloutStepNumber, getObservedGeneration, getWorkloadRolloutActivity, isArgoRolloutResource, isRolloutActivityVisible, rolloutActivityBadge, type WorkloadRolloutActivity } from '../../utils/workload-rollout'
|
|
5
7
|
|
|
6
8
|
// Import functions from sub-modules used internally by getCellFilterValue
|
|
7
9
|
import { getCertificateStatus, getCertificateRequestStatus, getClusterIssuerStatus, getClusterIssuerType, getOrderState, getChallengeState, getChallengeType } from './resource-utils-certmanager'
|
|
@@ -12,10 +14,23 @@ import { getArgoApplicationStatus, getArgoApplicationSetStatus, getArgoApplicati
|
|
|
12
14
|
import { getPolicyReportStatus as _getPolicyReportStatus, getKyvernoPolicyStatus as _getKyvernoPolicyStatus } from './resource-utils-kyverno'
|
|
13
15
|
import { getResourceClaimStatus as _getResourceClaimStatus, getResourceClaimDeviceClasses as _getResourceClaimDeviceClasses, getResourceClaimTemplateDeviceClasses as _getResourceClaimTemplateDeviceClasses, getResourceClaimAllocation as _getResourceClaimAllocation, getResourceClaimReservedFor as _getResourceClaimReservedFor } from './resource-utils-dra'
|
|
14
16
|
import { getNvidiaClusterPolicyStatus as _getNvidiaClusterPolicyStatus, getNvidiaClusterPolicyEnabledComponents as _getNvidiaClusterPolicyEnabledComponents, getNvidiaDriverStatus as _getNvidiaDriverStatus } from './resource-utils-nvidia'
|
|
17
|
+
import { getClusterQueueStatus as _getClusterQueueStatus, getLocalQueueStatus as _getLocalQueueStatus, getKueueWorkloadStatus as _getKueueWorkloadStatus, getResourceFlavorStatus as _getResourceFlavorStatus, getAdmissionCheckStatus as _getAdmissionCheckStatus, getProvisioningRequestStatus as _getProvisioningRequestStatus } from './resource-utils-kueue'
|
|
18
|
+
import { getRayClusterStatus as _getRayClusterStatus, getRayJobStatus as _getRayJobStatus, getRayServiceStatus as _getRayServiceStatus, getRayCronJobStatus as _getRayCronJobStatus } from './resource-utils-ray'
|
|
19
|
+
import { getLeaderWorkerSetStatus as _getLeaderWorkerSetStatus, getJobSetStatus as _getJobSetStatus } from './resource-utils-jobset-lws'
|
|
20
|
+
import { getInferenceServiceStatus as _getInferenceServiceStatus, getServingRuntimeStatus as _getServingRuntimeStatus, getInferenceGraphStatus as _getInferenceGraphStatus, getTrainedModelStatus as _getTrainedModelStatus, getLLMInferenceServiceStatus as _getLLMInferenceServiceStatus } from './resource-utils-kserve'
|
|
21
|
+
import { getInferencePoolStatus as _getInferencePoolStatus, getInferenceObjectiveStatus as _getInferenceObjectiveStatus } from './resource-utils-inference-gateway'
|
|
22
|
+
import { getVolcanoJobStatus as _getVolcanoJobStatus, getVolcanoQueueStatus as _getVolcanoQueueStatus, getVolcanoPodGroupStatus as _getVolcanoPodGroupStatus, getJobFlowStatus as _getJobFlowStatus, getJobTemplateStatus as _getJobTemplateStatus } from './resource-utils-volcano'
|
|
23
|
+
import { getKaiQueueStatus as _getKaiQueueStatus, getKaiPodGroupStatus as _getKaiPodGroupStatus } from './resource-utils-kai'
|
|
24
|
+
import { getKaitoWorkspaceStatus as _getKaitoWorkspaceStatus, getRAGEngineStatus as _getRAGEngineStatus } from './resource-utils-kaito'
|
|
25
|
+
import { getNIMServiceStatus as _getNIMServiceStatus, getNIMCacheStatus as _getNIMCacheStatus, getNIMPipelineStatus as _getNIMPipelineStatus } from './resource-utils-nim'
|
|
26
|
+
import { getAMDDeviceConfigStatus as _getAMDDeviceConfigStatus } from './resource-utils-amd-gpu'
|
|
27
|
+
import { getPyTorchJobStatus as _getPyTorchJobStatus, getTFJobStatus as _getTFJobStatus, getMPIJobStatus as _getMPIJobStatus, getTrainJobStatus as _getTrainJobStatus } from './resource-utils-kubeflow-training'
|
|
15
28
|
import { getBackupStatus as _getBackupStatus, getRestoreStatus as _getRestoreStatus, getScheduleStatus as _getScheduleStatus, getBSLStatus as _getBSLStatus, getBackupRepositoryStatus as _getBackupRepositoryStatus } from './resource-utils-velero'
|
|
16
29
|
import { getExternalSecretStatus as _getExternalSecretStatus, getClusterExternalSecretStatus as _getClusterExternalSecretStatus, getSecretStoreStatus as _getSecretStoreStatus, getClusterSecretStoreStatus as _getClusterSecretStoreStatus, getSecretStoreProviderType as _getSecretStoreProviderType } from './resource-utils-eso'
|
|
17
30
|
import { getHPATableState, hpaStatusFromState } from './resource-utils-hpa'
|
|
18
31
|
import { getCNPGClusterStatus as _getCNPGClusterStatus, getCNPGBackupStatus as _getCNPGBackupStatus, getCNPGScheduledBackupStatus as _getCNPGScheduledBackupStatus, getCNPGPoolerStatus as _getCNPGPoolerStatus, isApiGroup as _isApiGroup, CNPG_GROUP as _CNPG_GROUP } from './resource-utils-cnpg'
|
|
32
|
+
import { getGenericResourceStatus } from './generic-status'
|
|
33
|
+
import { getIstioGatewayStatus as _getIstioGatewayStatus, getIstioGatewayServerCount as _getIstioGatewayServerCount, getIstioGatewaySelectorString as _getIstioGatewaySelectorString } from './resource-utils-istio'
|
|
19
34
|
import { getCalicoIPPoolAllowedUses, getCalicoIPPoolBlockSize, getCalicoIPPoolEncapsulation, getCalicoPolicyNamespaceSelector, getCalicoPolicyServiceAccountSelector, getCalicoPolicyTypes, isCalicoApiVersion, isCalicoPolicyResource } from './resource-utils-calico'
|
|
20
35
|
|
|
21
36
|
// ============================================================================
|
|
@@ -762,8 +777,8 @@ export function getContainerSquareStates(pod: any): ContainerSquareState[] {
|
|
|
762
777
|
*/
|
|
763
778
|
function isConverging(resource: any): boolean {
|
|
764
779
|
const generation = resource?.metadata?.generation
|
|
765
|
-
const observed = resource
|
|
766
|
-
return typeof generation === 'number' &&
|
|
780
|
+
const observed = getObservedGeneration(resource)
|
|
781
|
+
return typeof generation === 'number' && observed > 0 && observed < generation
|
|
767
782
|
}
|
|
768
783
|
|
|
769
784
|
/** Whether the pod was created by a batch Job. The owner's API group is checked
|
|
@@ -787,8 +802,8 @@ export function getWorkloadStatus(resource: any, kind: string): StatusBadge {
|
|
|
787
802
|
// 0 desired = the node selector matches no nodes — intentional/idle, not a
|
|
788
803
|
// fault and not "unknown" (matches pkg/health.Workload). Sky.
|
|
789
804
|
if (desired === 0) return { text: '0 nodes', color: healthColors.neutral, level: 'neutral' }
|
|
790
|
-
if (ready
|
|
791
|
-
return { text: `${ready}/${desired}`, color: healthColors.healthy, level: 'healthy' }
|
|
805
|
+
if (ready >= desired && updated >= desired) {
|
|
806
|
+
return { text: `${Math.min(ready, desired)}/${desired}`, color: healthColors.healthy, level: 'healthy' }
|
|
792
807
|
}
|
|
793
808
|
// Convergence grace, same as the replica kinds below. pkg/health.Workload
|
|
794
809
|
// passes this signal for DaemonSets too, so omitting it here would render a
|
|
@@ -803,7 +818,7 @@ export function getWorkloadStatus(resource: any, kind: string): StatusBadge {
|
|
|
803
818
|
}
|
|
804
819
|
|
|
805
820
|
// Deployment, StatefulSet, ReplicaSet
|
|
806
|
-
const desired = spec.replicas ??
|
|
821
|
+
const desired = spec.replicas ?? 1
|
|
807
822
|
const ready = status.readyReplicas || 0
|
|
808
823
|
const updated = status.updatedReplicas || 0
|
|
809
824
|
const available = status.availableReplicas || 0
|
|
@@ -818,17 +833,17 @@ export function getWorkloadStatus(resource: any, kind: string): StatusBadge {
|
|
|
818
833
|
// an outage. Neutral, never healthy: declining to call it broken is not the
|
|
819
834
|
// same as calling it fine. Guarded on "not already fully ready" so a
|
|
820
835
|
// converged workload still takes the healthy path below, matching Go's order.
|
|
821
|
-
if (isConverging(resource) && !(ready
|
|
836
|
+
if (isConverging(resource) && !(ready >= desired && available >= desired)) {
|
|
822
837
|
return { text: `${ready}/${desired}`, color: healthColors.neutral, level: 'neutral' }
|
|
823
838
|
}
|
|
824
839
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
return { text: `Updating ${updated}/${desired}`, color: healthColors.degraded, level: 'degraded' }
|
|
840
|
+
if (ready >= desired && available >= desired) {
|
|
841
|
+
return { text: `${Math.min(ready, desired)}/${desired}`, color: healthColors.healthy, level: 'healthy' }
|
|
828
842
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
843
|
+
const partition = kind === 'statefulsets' ? (spec.updateStrategy?.rollingUpdate?.partition ?? 0) : 0
|
|
844
|
+
const updateTarget = Math.max(0, desired - partition)
|
|
845
|
+
if (updated < updateTarget && updated > 0) {
|
|
846
|
+
return { text: `Updating ${updated}/${updateTarget}`, color: healthColors.degraded, level: 'degraded' }
|
|
832
847
|
}
|
|
833
848
|
if (ready > 0) {
|
|
834
849
|
return { text: `${ready}/${desired}`, color: healthColors.degraded, level: 'degraded' }
|
|
@@ -836,6 +851,50 @@ export function getWorkloadStatus(resource: any, kind: string): StatusBadge {
|
|
|
836
851
|
return { text: `${ready}/${desired}`, color: healthColors.unhealthy, level: 'unhealthy' }
|
|
837
852
|
}
|
|
838
853
|
|
|
854
|
+
export function getWorkloadDisplayStatus(
|
|
855
|
+
resource: any,
|
|
856
|
+
kind: string,
|
|
857
|
+
workloadPods?: WorkloadPodInfo[],
|
|
858
|
+
): { activity: WorkloadRolloutActivity; status: StatusBadge } {
|
|
859
|
+
const normalizedKind = kind.toLowerCase().replace(/s$/, '')
|
|
860
|
+
const activity = getWorkloadRolloutActivity(resource, normalizedKind, workloadPods)
|
|
861
|
+
if (normalizedKind === 'rollout' && !isArgoRolloutResource(resource)) {
|
|
862
|
+
return { activity, status: getRolloutStatus(resource) }
|
|
863
|
+
}
|
|
864
|
+
const healthStatus = getWorkloadStatus(resource, `${normalizedKind}s`)
|
|
865
|
+
if (!isRolloutActivityVisible(activity)) {
|
|
866
|
+
return { activity, status: healthStatus }
|
|
867
|
+
}
|
|
868
|
+
const status: StatusBadge = rolloutActivityBadge(activity)
|
|
869
|
+
if (workloadStatusLevelRank[healthStatus.level] > workloadStatusLevelRank[status.level]) {
|
|
870
|
+
status.level = healthStatus.level
|
|
871
|
+
status.color = healthStatus.color
|
|
872
|
+
status.text = `${workloadStatusLabel(healthStatus)} · ${status.text}`
|
|
873
|
+
}
|
|
874
|
+
return { activity, status }
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
export function workloadStatusLabel(status: StatusBadge): string {
|
|
878
|
+
if (status.text === 'Scaled to 0') return status.text
|
|
879
|
+
return {
|
|
880
|
+
healthy: 'Healthy',
|
|
881
|
+
degraded: 'Degraded',
|
|
882
|
+
alert: 'Alert',
|
|
883
|
+
unhealthy: 'Unhealthy',
|
|
884
|
+
neutral: 'Neutral',
|
|
885
|
+
unknown: 'Unknown',
|
|
886
|
+
}[status.level]
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const workloadStatusLevelRank: Record<HealthLevel, number> = {
|
|
890
|
+
neutral: 0,
|
|
891
|
+
healthy: 0,
|
|
892
|
+
unknown: 2,
|
|
893
|
+
degraded: 3,
|
|
894
|
+
alert: 4,
|
|
895
|
+
unhealthy: 5,
|
|
896
|
+
}
|
|
897
|
+
|
|
839
898
|
/** Detect problems for Deployments, StatefulSets, DaemonSets. Parallel to getPodProblems. */
|
|
840
899
|
export function getWorkloadProblems(resource: any, kind: string): PodProblem[] {
|
|
841
900
|
const problems: PodProblem[] = []
|
|
@@ -1232,7 +1291,7 @@ export function getHPAStatus(hpa: any): StatusBadge {
|
|
|
1232
1291
|
export function getHPAReplicas(hpa: any): { current: number; min: number; max: number } {
|
|
1233
1292
|
return {
|
|
1234
1293
|
current: hpa.status?.currentReplicas || 0,
|
|
1235
|
-
min: hpa.spec?.minReplicas
|
|
1294
|
+
min: hpa.spec?.minReplicas ?? 1,
|
|
1236
1295
|
max: hpa.spec?.maxReplicas || 0,
|
|
1237
1296
|
}
|
|
1238
1297
|
}
|
|
@@ -1478,19 +1537,10 @@ export function getPVCAccessModes(pvc: any): string {
|
|
|
1478
1537
|
// ============================================================================
|
|
1479
1538
|
|
|
1480
1539
|
export function getRolloutStatus(rollout: any): StatusBadge {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
case 'Healthy':
|
|
1484
|
-
return { text: 'Healthy', color: healthColors.healthy, level: 'healthy' }
|
|
1485
|
-
case 'Paused':
|
|
1486
|
-
return { text: 'Paused', color: healthColors.degraded, level: 'degraded' }
|
|
1487
|
-
case 'Progressing':
|
|
1488
|
-
return { text: 'Progressing', color: healthColors.degraded, level: 'degraded' }
|
|
1489
|
-
case 'Degraded':
|
|
1490
|
-
return { text: 'Degraded', color: healthColors.unhealthy, level: 'unhealthy' }
|
|
1491
|
-
default:
|
|
1492
|
-
return { text: phase, color: healthColors.unknown, level: 'unknown' }
|
|
1540
|
+
if (!isArgoRolloutResource(rollout)) {
|
|
1541
|
+
return { text: rollout.status?.phase || 'Unknown', color: healthColors.unknown, level: 'unknown' }
|
|
1493
1542
|
}
|
|
1543
|
+
return rolloutActivityBadge(getWorkloadRolloutActivity(rollout, 'rollout'))
|
|
1494
1544
|
}
|
|
1495
1545
|
|
|
1496
1546
|
export function getAnalysisRunStatus(run: any): StatusBadge {
|
|
@@ -1543,9 +1593,9 @@ export function getRolloutReady(rollout: any): string {
|
|
|
1543
1593
|
|
|
1544
1594
|
export function getRolloutStep(rollout: any): string | null {
|
|
1545
1595
|
const steps = rollout.spec?.strategy?.canary?.steps || []
|
|
1546
|
-
const
|
|
1547
|
-
if (
|
|
1548
|
-
return `${
|
|
1596
|
+
const stepNumber = getArgoRolloutStepNumber(rollout)
|
|
1597
|
+
if (stepNumber === null) return null
|
|
1598
|
+
return `${stepNumber}/${steps.length}`
|
|
1549
1599
|
}
|
|
1550
1600
|
|
|
1551
1601
|
// ============================================================================
|
|
@@ -2134,13 +2184,16 @@ export function getCellFilterValue(resource: any, column: string, kind: string):
|
|
|
2134
2184
|
return resource.spec?.type || resource.type || ''
|
|
2135
2185
|
case 'status':
|
|
2136
2186
|
if (kindLower === 'pods') return getPodStatus(resource).text
|
|
2137
|
-
if (['deployments', 'statefulsets', 'daemonsets', 'replicasets'].includes(kindLower)) {
|
|
2138
|
-
|
|
2139
|
-
if (
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2187
|
+
if (['deployments', 'statefulsets', 'daemonsets', 'replicasets', 'rollouts'].includes(kindLower)) {
|
|
2188
|
+
let status: StatusBadge
|
|
2189
|
+
if (kindLower === 'replicasets') {
|
|
2190
|
+
status = getWorkloadStatus(resource, kindLower)
|
|
2191
|
+
} else {
|
|
2192
|
+
const display = getWorkloadDisplayStatus(resource, kindLower)
|
|
2193
|
+
if (isRolloutActivityVisible(display.activity)) return display.status.text
|
|
2194
|
+
status = display.status
|
|
2195
|
+
}
|
|
2196
|
+
return workloadStatusLabel(status)
|
|
2144
2197
|
}
|
|
2145
2198
|
if (kindLower === 'nodes') return getNodeStatus(resource).text
|
|
2146
2199
|
if (kindLower === 'jobs') return getJobStatus(resource).text
|
|
@@ -2150,7 +2203,6 @@ export function getCellFilterValue(resource: any, column: string, kind: string):
|
|
|
2150
2203
|
if (kindLower === 'clusterissuers' || kindLower === 'issuers') return getClusterIssuerStatus(resource).text
|
|
2151
2204
|
if (kindLower === 'persistentvolumeclaims') return getPVCStatus(resource).text
|
|
2152
2205
|
if (kindLower === 'persistentvolumes') return getPVStatus(resource).text
|
|
2153
|
-
if (kindLower === 'rollouts') return getRolloutStatus(resource).text
|
|
2154
2206
|
if (kindLower === 'analysisruns') return getAnalysisRunStatus(resource).text
|
|
2155
2207
|
if (kindLower === 'workflows') return getWorkflowStatus(resource).text
|
|
2156
2208
|
if (kindLower === 'cronworkflows') return getCronWorkflowStatus(resource).text
|
|
@@ -2177,7 +2229,52 @@ export function getCellFilterValue(resource: any, column: string, kind: string):
|
|
|
2177
2229
|
if (kindLower === 'nvidiaclusterpolicies') return _getNvidiaClusterPolicyStatus(resource).text
|
|
2178
2230
|
if (kindLower === 'nvidiadrivers') return _getNvidiaDriverStatus(resource).text
|
|
2179
2231
|
if (kindLower === 'resourceclaims') return _getResourceClaimStatus(resource).text
|
|
2180
|
-
if (kindLower === '
|
|
2232
|
+
if (kindLower === 'clusterqueues') return _getClusterQueueStatus(resource).text
|
|
2233
|
+
if (kindLower === 'localqueues') return _getLocalQueueStatus(resource).text
|
|
2234
|
+
if (kindLower === 'workloads') return _getKueueWorkloadStatus(resource).text
|
|
2235
|
+
if (kindLower === 'resourceflavors') return _getResourceFlavorStatus(resource).text
|
|
2236
|
+
if (kindLower === 'admissionchecks') return _getAdmissionCheckStatus(resource).text
|
|
2237
|
+
if (kindLower === 'provisioningrequests') return _getProvisioningRequestStatus(resource).text
|
|
2238
|
+
if (kindLower === 'rayclusters') return _getRayClusterStatus(resource).text
|
|
2239
|
+
if (kindLower === 'rayjobs') return _getRayJobStatus(resource).text
|
|
2240
|
+
if (kindLower === 'rayservices') return _getRayServiceStatus(resource).text
|
|
2241
|
+
if (kindLower === 'raycronjobs') return _getRayCronJobStatus(resource).text
|
|
2242
|
+
if (kindLower === 'leaderworkersets') return _getLeaderWorkerSetStatus(resource).text
|
|
2243
|
+
if (kindLower === 'jobsets') return _getJobSetStatus(resource).text
|
|
2244
|
+
if (kindLower === 'inferenceservices') return _getInferenceServiceStatus(resource).text
|
|
2245
|
+
if (kindLower === 'servingruntimes' || kindLower === 'clusterservingruntimes') return _getServingRuntimeStatus(resource).text
|
|
2246
|
+
if (kindLower === 'inferencegraphs') return _getInferenceGraphStatus(resource).text
|
|
2247
|
+
if (kindLower === 'trainedmodels') return _getTrainedModelStatus(resource).text
|
|
2248
|
+
if (kindLower === 'llminferenceservices') return _getLLMInferenceServiceStatus(resource).text
|
|
2249
|
+
if (kindLower === 'inferencepools') return _getInferencePoolStatus(resource).text
|
|
2250
|
+
if (kindLower === 'inferenceobjectives') return _getInferenceObjectiveStatus(resource).text
|
|
2251
|
+
if (kindLower === 'volcanojobs') return _getVolcanoJobStatus(resource).text
|
|
2252
|
+
if (kindLower === 'volcanoqueues') return _getVolcanoQueueStatus(resource).text
|
|
2253
|
+
if (kindLower === 'volcanopodgroups') return _getVolcanoPodGroupStatus(resource).text
|
|
2254
|
+
if (kindLower === 'jobflows') return _getJobFlowStatus(resource).text
|
|
2255
|
+
if (kindLower === 'jobtemplates') return _getJobTemplateStatus(resource).text
|
|
2256
|
+
if (kindLower === 'kaiqueues') return _getKaiQueueStatus(resource).text
|
|
2257
|
+
if (kindLower === 'kaipodgroups') return _getKaiPodGroupStatus(resource).text
|
|
2258
|
+
if (kindLower === 'kaitoworkspaces') return _getKaitoWorkspaceStatus(resource).text
|
|
2259
|
+
if (kindLower === 'ragengines') return _getRAGEngineStatus(resource).text
|
|
2260
|
+
if (kindLower === 'nimservices') return _getNIMServiceStatus(resource).text
|
|
2261
|
+
if (kindLower === 'nimcaches') return _getNIMCacheStatus(resource).text
|
|
2262
|
+
if (kindLower === 'nimpipelines') return _getNIMPipelineStatus(resource).text
|
|
2263
|
+
if (kindLower === 'deviceconfigs') return _getAMDDeviceConfigStatus(resource).text
|
|
2264
|
+
if (kindLower === 'pytorchjobs') return _getPyTorchJobStatus(resource).text
|
|
2265
|
+
if (kindLower === 'tfjobs') return _getTFJobStatus(resource).text
|
|
2266
|
+
if (kindLower === 'mpijobs') return _getMPIJobStatus(resource).text
|
|
2267
|
+
if (kindLower === 'trainjobs') return _getTrainJobStatus(resource).text
|
|
2268
|
+
// Positively gated, matching the cell dispatch: `backups` is reached here
|
|
2269
|
+
// only when the group is unknown to normalizeKindToPlural, and a third
|
|
2270
|
+
// vendor's Backup renders through the generic path — so the sort key has
|
|
2271
|
+
// to come from there too, or the column sorts on a Velero-derived string
|
|
2272
|
+
// the row never displayed.
|
|
2273
|
+
if (kindLower === 'backups') {
|
|
2274
|
+
if (_isApiGroup(resource.apiVersion, _CNPG_GROUP)) return _getCNPGBackupStatus(resource).text
|
|
2275
|
+
if (_isApiGroup(resource.apiVersion, 'velero.io')) return _getBackupStatus(resource).text
|
|
2276
|
+
return getGenericResourceStatus(resource)?.text ?? ''
|
|
2277
|
+
}
|
|
2181
2278
|
// Velero's Restore/Schedule keys are group-qualified (see
|
|
2182
2279
|
// GROUP_QUALIFIED_COLUMN_KEYS) because those plurals are shared with
|
|
2183
2280
|
// rancher/backup-restore-operator and others. The unqualified plural
|
|
@@ -2185,8 +2282,17 @@ export function getCellFilterValue(resource: any, column: string, kind: string):
|
|
|
2185
2282
|
// reader below rather than being filtered as though it were Velero.
|
|
2186
2283
|
if (kindLower === 'velerorestores') return _getRestoreStatus(resource).text
|
|
2187
2284
|
if (kindLower === 'veleroschedules') return _getScheduleStatus(resource).text
|
|
2188
|
-
|
|
2189
|
-
|
|
2285
|
+
// Same positive gate as `backups` above, for the same reason: the cell
|
|
2286
|
+
// dispatch sends a non-Velero resource to GenericCell, so the sort and
|
|
2287
|
+
// filter keys have to come from there too.
|
|
2288
|
+
if (kindLower === 'backupstoragelocations') {
|
|
2289
|
+
if (_isApiGroup(resource.apiVersion, 'velero.io')) return _getBSLStatus(resource).text
|
|
2290
|
+
return getGenericResourceStatus(resource)?.text ?? ''
|
|
2291
|
+
}
|
|
2292
|
+
if (kindLower === 'backuprepositories') {
|
|
2293
|
+
if (_isApiGroup(resource.apiVersion, 'velero.io')) return _getBackupRepositoryStatus(resource).text
|
|
2294
|
+
return getGenericResourceStatus(resource)?.text ?? ''
|
|
2295
|
+
}
|
|
2190
2296
|
if (kindLower === 'externalsecrets') return _getExternalSecretStatus(resource).text
|
|
2191
2297
|
if (kindLower === 'clusterexternalsecrets') return _getClusterExternalSecretStatus(resource).text
|
|
2192
2298
|
if (kindLower === 'secretstores') return _getSecretStoreStatus(resource).text
|
|
@@ -2203,14 +2309,17 @@ export function getCellFilterValue(resource: any, column: string, kind: string):
|
|
|
2203
2309
|
if (kindLower === 'cnpgbackups') return _getCNPGBackupStatus(resource).text
|
|
2204
2310
|
if (kindLower === 'scheduledbackups' && _isApiGroup(resource.apiVersion, _CNPG_GROUP)) return _getCNPGScheduledBackupStatus(resource).text
|
|
2205
2311
|
if (kindLower === 'poolers' && _isApiGroup(resource.apiVersion, _CNPG_GROUP)) return _getCNPGPoolerStatus(resource).text
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2312
|
+
if (kindLower === 'istiogateways') return _getIstioGatewayStatus(resource).text
|
|
2313
|
+
// Generic CRDs. Must read the same text the cell renders or the dropdown
|
|
2314
|
+
// offers strings that appear on no row — hence the shared derivation.
|
|
2315
|
+
return getGenericResourceStatus(resource)?.text ?? ''
|
|
2316
|
+
// Istio Gateway's own columns. Without these the filter dropdown is empty
|
|
2317
|
+
// for a column the table is visibly populating.
|
|
2318
|
+
case 'servers':
|
|
2319
|
+
if (kindLower === 'istiogateways') return String(_getIstioGatewayServerCount(resource))
|
|
2320
|
+
return ''
|
|
2321
|
+
case 'selector':
|
|
2322
|
+
if (kindLower === 'istiogateways') return _getIstioGatewaySelectorString(resource)
|
|
2214
2323
|
return ''
|
|
2215
2324
|
case 'state':
|
|
2216
2325
|
if (kindLower === 'orders') return getOrderState(resource).text
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { isColumnFilterableByDistinctCount, SKIP_FILTER_COLUMNS } from './ResourcesView'
|
|
2
|
+
import { getCellFilterKind, hasCuratedColumns, isColumnFilterableByDistinctCount, normalizeKindToPlural, SKIP_FILTER_COLUMNS } from './ResourcesView'
|
|
3
3
|
|
|
4
4
|
describe('isColumnFilterableByDistinctCount', () => {
|
|
5
5
|
// Caps were tuned after a customer-visible regression where the namespace
|
|
@@ -72,3 +72,29 @@ describe('isColumnFilterableByDistinctCount', () => {
|
|
|
72
72
|
})
|
|
73
73
|
})
|
|
74
74
|
})
|
|
75
|
+
|
|
76
|
+
describe('GPU ecosystem column routing', () => {
|
|
77
|
+
it('uses specialized columns only for the owning API group', () => {
|
|
78
|
+
expect(hasCuratedColumns('workloads', 'kueue.x-k8s.io')).toBe(true)
|
|
79
|
+
expect(hasCuratedColumns('workloads', 'scheduling.k8s.io')).toBe(false)
|
|
80
|
+
expect(hasCuratedColumns('Workload', 'scheduling.k8s.io')).toBe(false)
|
|
81
|
+
expect(hasCuratedColumns('deviceconfigs', 'amd.com')).toBe(true)
|
|
82
|
+
expect(hasCuratedColumns('deviceconfigs', 'example.io')).toBe(false)
|
|
83
|
+
expect(getCellFilterKind('workloads', 'kueue.x-k8s.io')).toBe('workloads')
|
|
84
|
+
expect(getCellFilterKind('workloads', 'scheduling.k8s.io')).toBe('__generic_workloads')
|
|
85
|
+
expect(getCellFilterKind('deviceconfigs', 'example.io')).toBe('__generic_deviceconfigs')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('keeps core, Volcano, and foreign Jobs on separate column paths', () => {
|
|
89
|
+
expect(normalizeKindToPlural('jobs', 'batch')).toBe('jobs')
|
|
90
|
+
expect(normalizeKindToPlural('jobs', 'batch.volcano.sh')).toBe('volcanojobs')
|
|
91
|
+
expect(normalizeKindToPlural('jobs', 'example.io')).toBe('__generic_jobs')
|
|
92
|
+
expect(normalizeKindToPlural('Job', 'example.io')).toBe('__generic_job')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('recognizes both the current and deployed InferenceObjective groups', () => {
|
|
96
|
+
expect(hasCuratedColumns('inferenceobjectives', 'llm-d.ai')).toBe(true)
|
|
97
|
+
expect(hasCuratedColumns('inferenceobjectives', 'inference.networking.x-k8s.io')).toBe(true)
|
|
98
|
+
expect(hasCuratedColumns('inferenceobjectives', 'example.io')).toBe(false)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { dirname, resolve } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { getWorkloadRolloutActivity, rolloutMayAdvanceAutomatically, type WorkloadRolloutPhase } from '../../utils/workload-rollout'
|
|
6
|
+
|
|
7
|
+
interface GoldenVector {
|
|
8
|
+
name: string
|
|
9
|
+
kind: string
|
|
10
|
+
phase: WorkloadRolloutPhase
|
|
11
|
+
active: boolean
|
|
12
|
+
manual: boolean
|
|
13
|
+
label: string
|
|
14
|
+
detail: string
|
|
15
|
+
desired: number
|
|
16
|
+
updated: number
|
|
17
|
+
ready: number
|
|
18
|
+
available: number
|
|
19
|
+
object: any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const here = dirname(fileURLToPath(import.meta.url))
|
|
23
|
+
const fixturePath = resolve(here, '../../../../../pkg/health/testdata/workload_rollout_vectors.json')
|
|
24
|
+
const vectors: GoldenVector[] = JSON.parse(readFileSync(fixturePath, 'utf8')).vectors
|
|
25
|
+
|
|
26
|
+
describe('workload rollout golden vectors', () => {
|
|
27
|
+
it('loads the same fixture as pkg/health', () => {
|
|
28
|
+
expect(vectors.length).toBeGreaterThan(0)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
for (const vector of vectors) {
|
|
32
|
+
it(`${vector.kind}: ${vector.name}`, () => {
|
|
33
|
+
const result = getWorkloadRolloutActivity(vector.object, vector.kind)
|
|
34
|
+
expect(result).toEqual({
|
|
35
|
+
phase: vector.phase,
|
|
36
|
+
active: vector.active,
|
|
37
|
+
manual: vector.manual,
|
|
38
|
+
label: vector.label,
|
|
39
|
+
detail: vector.detail,
|
|
40
|
+
desired: vector.desired,
|
|
41
|
+
updated: vector.updated,
|
|
42
|
+
ready: vector.ready,
|
|
43
|
+
available: vector.available,
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
it('polls only states that can advance without operator action', () => {
|
|
49
|
+
const base = { active: true, manual: false, label: 'Rolling out', desired: 1, updated: 0, ready: 1, available: 1 }
|
|
50
|
+
expect(rolloutMayAdvanceAutomatically({ ...base, phase: 'progressing' })).toBe(true)
|
|
51
|
+
expect(rolloutMayAdvanceAutomatically({ ...base, phase: 'waiting', label: 'Waiting for new revision' })).toBe(true)
|
|
52
|
+
expect(rolloutMayAdvanceAutomatically({ ...base, phase: 'waiting', manual: true, label: 'Waiting for Pod restart' })).toBe(false)
|
|
53
|
+
expect(rolloutMayAdvanceAutomatically({ ...base, phase: 'paused', label: 'Rollout paused' })).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -20,6 +20,7 @@ import { Tooltip } from '../ui/Tooltip'
|
|
|
20
20
|
import type { SelectedResource } from '../../types'
|
|
21
21
|
import { resourceToYaml } from '../../utils/yaml'
|
|
22
22
|
import { triggerDownload } from '../../utils/download'
|
|
23
|
+
import { isCoreBatchJob } from '../../utils/api-resources'
|
|
23
24
|
|
|
24
25
|
// ============================================================================
|
|
25
26
|
// SUCCESS ANIMATION
|
|
@@ -45,6 +46,7 @@ export function SaveSuccessAnimation() {
|
|
|
45
46
|
// Get edit warning for resource types with limited editability
|
|
46
47
|
function getEditWarning(
|
|
47
48
|
kind: string,
|
|
49
|
+
group?: string,
|
|
48
50
|
): { message: string; tip: string; learnMoreUrl?: string } | null {
|
|
49
51
|
const k = kind.toLowerCase()
|
|
50
52
|
if (k === 'pods' || k === 'pod') {
|
|
@@ -55,7 +57,7 @@ function getEditWarning(
|
|
|
55
57
|
'https://kubernetes.io/docs/concepts/workloads/pods/#pod-update-and-replacement',
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
|
-
if (k
|
|
60
|
+
if (isCoreBatchJob(k, group)) {
|
|
59
61
|
return {
|
|
60
62
|
message: 'Jobs cannot be modified after creation.',
|
|
61
63
|
tip: 'Delete and recreate the Job to make changes.',
|
|
@@ -351,7 +353,7 @@ export function EditableYamlView({
|
|
|
351
353
|
}, [])
|
|
352
354
|
|
|
353
355
|
const yamlContent = yamlStringify(data, { lineWidth: 0, indent: 2 })
|
|
354
|
-
const editWarning = getEditWarning(resource.kind)
|
|
356
|
+
const editWarning = getEditWarning(resource.kind, resource.group)
|
|
355
357
|
const formattedError = saveError ? formatSaveError(saveError) : null
|
|
356
358
|
const isPending = (isSaving ?? false) || (isPreviewing ?? false)
|
|
357
359
|
|