@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,292 @@
|
|
|
1
|
+
import type { WorkloadPodInfo } from '../types/core'
|
|
2
|
+
|
|
3
|
+
export type WorkloadRolloutPhase =
|
|
4
|
+
| 'idle'
|
|
5
|
+
| 'applying'
|
|
6
|
+
| 'progressing'
|
|
7
|
+
| 'waiting'
|
|
8
|
+
| 'paused'
|
|
9
|
+
| 'partition-reached'
|
|
10
|
+
| 'stalled'
|
|
11
|
+
|
|
12
|
+
export interface WorkloadRolloutActivity {
|
|
13
|
+
phase: WorkloadRolloutPhase
|
|
14
|
+
active: boolean
|
|
15
|
+
manual: boolean
|
|
16
|
+
label: string
|
|
17
|
+
detail?: string
|
|
18
|
+
desired: number
|
|
19
|
+
updated: number
|
|
20
|
+
ready: number
|
|
21
|
+
available: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isArgoRolloutResource(resource: any): boolean {
|
|
25
|
+
return String(resource?.apiVersion || '').startsWith('argoproj.io/')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function rolloutMayAdvanceAutomatically(activity: WorkloadRolloutActivity): boolean {
|
|
29
|
+
return activity.phase === 'applying' ||
|
|
30
|
+
activity.phase === 'progressing' ||
|
|
31
|
+
(activity.phase === 'waiting' && !activity.manual)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isRolloutActivityVisible(activity: WorkloadRolloutActivity): boolean {
|
|
35
|
+
return activity.active || activity.phase === 'stalled'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getObservedGeneration(resource: any): number {
|
|
39
|
+
const observed = resource?.status?.observedGeneration
|
|
40
|
+
if (typeof observed === 'number' && Number.isFinite(observed)) return observed
|
|
41
|
+
return typeof observed === 'string' && /^\d+$/.test(observed)
|
|
42
|
+
? Number.parseInt(observed, 10)
|
|
43
|
+
: 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getWorkloadRolloutActivity(
|
|
47
|
+
resource: any,
|
|
48
|
+
kind?: string,
|
|
49
|
+
workloadPods?: WorkloadPodInfo[],
|
|
50
|
+
): WorkloadRolloutActivity {
|
|
51
|
+
const normalized = (kind || resource?.kind || '').toLowerCase().replace(/s$/, '')
|
|
52
|
+
if (normalized === 'rollout' && !isArgoRolloutResource(resource)) {
|
|
53
|
+
return activity('idle', false, false, 'Stable', '', 0, 0, 0, 0)
|
|
54
|
+
}
|
|
55
|
+
let result: WorkloadRolloutActivity
|
|
56
|
+
switch (normalized) {
|
|
57
|
+
case 'deployment':
|
|
58
|
+
result = deploymentActivity(resource)
|
|
59
|
+
break
|
|
60
|
+
case 'statefulset':
|
|
61
|
+
result = statefulSetActivity(resource)
|
|
62
|
+
break
|
|
63
|
+
case 'daemonset':
|
|
64
|
+
result = daemonSetActivity(resource)
|
|
65
|
+
break
|
|
66
|
+
case 'rollout':
|
|
67
|
+
result = argoRolloutActivity(resource)
|
|
68
|
+
break
|
|
69
|
+
default:
|
|
70
|
+
result = activity('idle', false, false, 'Stable', '', 0, 0, 0, 0)
|
|
71
|
+
}
|
|
72
|
+
return withUpdatedRevisionFailure(result, workloadPods)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function deploymentActivity(resource: any): WorkloadRolloutActivity {
|
|
76
|
+
const spec = resource?.spec || {}
|
|
77
|
+
const status = resource?.status || {}
|
|
78
|
+
const desired = spec.replicas ?? 1
|
|
79
|
+
const updated = status.updatedReplicas ?? 0
|
|
80
|
+
const ready = status.readyReplicas ?? 0
|
|
81
|
+
const available = status.availableReplicas ?? 0
|
|
82
|
+
const base = activity('idle', false, false, 'Stable', '', desired, updated, ready, available)
|
|
83
|
+
const progress = (status.conditions || []).find((condition: any) => condition.type === 'Progressing')
|
|
84
|
+
|
|
85
|
+
if ((status.observedGeneration ?? 0) < (resource?.metadata?.generation ?? 0)) {
|
|
86
|
+
return merge(base, 'applying', true, 'Applying change', 'Waiting for the Deployment controller to observe generation')
|
|
87
|
+
}
|
|
88
|
+
if (progress?.status === 'False' || progress?.reason === 'ProgressDeadlineExceeded') {
|
|
89
|
+
return merge(base, 'stalled', false, 'Rollout stalled', progress.message || 'Progress deadline exceeded')
|
|
90
|
+
}
|
|
91
|
+
const old = Math.max(0, (status.replicas ?? 0) - updated)
|
|
92
|
+
const pending = updated < desired || old > 0
|
|
93
|
+
if (spec.paused) {
|
|
94
|
+
return merge(base, 'paused', pending, 'Rollout paused', pending ? `Paused at ${updated}/${desired} updated` : 'No rollout is pending')
|
|
95
|
+
}
|
|
96
|
+
if (desired === 0) return merge(base, 'idle', false, 'Scaled to zero', 'No replicas requested')
|
|
97
|
+
if (!pending) return merge(base, 'idle', false, 'Stable', `${available}/${desired} available`)
|
|
98
|
+
if (spec.strategy?.type === 'Recreate') {
|
|
99
|
+
if (updated === 0 && old > 0) {
|
|
100
|
+
return merge(base, 'waiting', true, 'Replacing replicas', `Waiting for old replicas to stop · ${available} available`)
|
|
101
|
+
}
|
|
102
|
+
return updated === 0
|
|
103
|
+
? merge(base, 'waiting', true, 'Waiting for new revision', replicaDetail(updated, desired, available))
|
|
104
|
+
: merge(base, 'progressing', true, 'Replacing replicas', replicaDetail(updated, desired, available))
|
|
105
|
+
}
|
|
106
|
+
if (updated === 0) return merge(base, 'waiting', true, 'Waiting for new revision', replicaDetail(updated, desired, available))
|
|
107
|
+
return old === 0 && updated === (status.replicas ?? 0)
|
|
108
|
+
? merge(base, 'progressing', true, 'Scaling', replicaDetail(updated, desired, available))
|
|
109
|
+
: merge(base, 'progressing', true, 'Rolling out', replicaDetail(updated, desired, available))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function statefulSetActivity(resource: any): WorkloadRolloutActivity {
|
|
113
|
+
const spec = resource?.spec || {}
|
|
114
|
+
const status = resource?.status || {}
|
|
115
|
+
const desired = spec.replicas ?? 1
|
|
116
|
+
const updated = status.updatedReplicas ?? 0
|
|
117
|
+
const ready = status.readyReplicas ?? 0
|
|
118
|
+
const base = activity('idle', false, false, 'Stable', '', desired, updated, ready, ready)
|
|
119
|
+
|
|
120
|
+
if ((status.observedGeneration ?? 0) < (resource?.metadata?.generation ?? 0)) {
|
|
121
|
+
return merge(base, 'applying', true, 'Applying change', 'Waiting for the StatefulSet controller to observe generation')
|
|
122
|
+
}
|
|
123
|
+
if (desired === 0) return merge(base, 'idle', false, 'Scaled to zero', 'No replicas requested')
|
|
124
|
+
if (spec.updateStrategy?.type === 'OnDelete') {
|
|
125
|
+
return updated === desired && ready === desired
|
|
126
|
+
? merge(base, 'idle', false, 'Stable', `${ready}/${desired} ready`)
|
|
127
|
+
: mergeManual(base, 'waiting', true, 'Waiting for Pod restart', `OnDelete strategy · ${updated}/${desired} updated`)
|
|
128
|
+
}
|
|
129
|
+
const partition = spec.updateStrategy?.rollingUpdate?.partition ?? 0
|
|
130
|
+
const target = Math.max(0, desired - partition)
|
|
131
|
+
if (updated >= target) {
|
|
132
|
+
const pendingRevision = Boolean(status.currentRevision && status.updateRevision && status.currentRevision !== status.updateRevision)
|
|
133
|
+
return partition > 0 && pendingRevision
|
|
134
|
+
? merge(base, 'partition-reached', false, 'Partition reached', `${partition}/${desired} Pods intentionally retained`)
|
|
135
|
+
: merge(base, 'idle', false, 'Stable', `${ready}/${desired} ready`)
|
|
136
|
+
}
|
|
137
|
+
const detail = `${updated}/${target} target Pods updated · ${ready}/${desired} ready`
|
|
138
|
+
return updated === 0
|
|
139
|
+
? merge(base, 'waiting', true, 'Waiting for new revision', detail)
|
|
140
|
+
: merge(base, 'progressing', true, 'Rolling out', detail)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function daemonSetActivity(resource: any): WorkloadRolloutActivity {
|
|
144
|
+
const spec = resource?.spec || {}
|
|
145
|
+
const status = resource?.status || {}
|
|
146
|
+
const desired = status.desiredNumberScheduled ?? 0
|
|
147
|
+
const updated = status.updatedNumberScheduled ?? 0
|
|
148
|
+
const ready = status.numberReady ?? 0
|
|
149
|
+
const available = status.numberAvailable ?? 0
|
|
150
|
+
const base = activity('idle', false, false, 'Stable', '', desired, updated, ready, available)
|
|
151
|
+
|
|
152
|
+
if ((status.observedGeneration ?? 0) < (resource?.metadata?.generation ?? 0)) {
|
|
153
|
+
return merge(base, 'applying', true, 'Applying change', 'Waiting for the DaemonSet controller to observe generation')
|
|
154
|
+
}
|
|
155
|
+
if (desired === 0) return merge(base, 'idle', false, 'No targets', 'Selector matches no nodes')
|
|
156
|
+
if (spec.updateStrategy?.type === 'OnDelete') {
|
|
157
|
+
return updated === desired && available === desired
|
|
158
|
+
? merge(base, 'idle', false, 'Stable', `${available}/${desired} available`)
|
|
159
|
+
: mergeManual(base, 'waiting', true, 'Waiting for Pod restart', `OnDelete strategy · ${updated}/${desired} updated`)
|
|
160
|
+
}
|
|
161
|
+
if (updated === desired) return merge(base, 'idle', false, 'Stable', `${available}/${desired} available`)
|
|
162
|
+
return updated === 0
|
|
163
|
+
? merge(base, 'waiting', true, 'Waiting for new revision', replicaDetail(updated, desired, available))
|
|
164
|
+
: merge(base, 'progressing', true, 'Rolling out', replicaDetail(updated, desired, available))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function argoRolloutActivity(resource: any): WorkloadRolloutActivity {
|
|
168
|
+
const spec = resource?.spec || {}
|
|
169
|
+
const status = resource?.status || {}
|
|
170
|
+
const desired = spec.replicas ?? ((status.replicas ?? 0) || 1)
|
|
171
|
+
const updated = status.updatedReplicas ?? 0
|
|
172
|
+
const ready = status.readyReplicas ?? 0
|
|
173
|
+
const available = status.availableReplicas ?? 0
|
|
174
|
+
const base = activity('idle', false, false, 'Stable', '', desired, updated, ready, available)
|
|
175
|
+
const detail = argoDetail(resource, updated, desired, available)
|
|
176
|
+
const observedGeneration = getObservedGeneration(resource)
|
|
177
|
+
if (observedGeneration > 0 && observedGeneration < (resource?.metadata?.generation ?? 0)) {
|
|
178
|
+
return merge(base, 'applying', true, 'Applying change', 'Waiting for the Rollout controller to observe generation')
|
|
179
|
+
}
|
|
180
|
+
const failedCondition = (status.conditions || []).find((condition: any) =>
|
|
181
|
+
(condition.type === 'InvalidSpec' && condition.status === 'True') ||
|
|
182
|
+
(condition.type === 'Progressing' && (condition.status === 'False' || condition.reason === 'ProgressDeadlineExceeded')),
|
|
183
|
+
)
|
|
184
|
+
if (failedCondition || status.abort) {
|
|
185
|
+
return merge(base, 'stalled', false, 'Rollout stalled', failedCondition?.message || failedCondition?.reason || status.message || (status.abort ? 'The Rollout was aborted' : '') || 'The Rollout controller reported a failed revision')
|
|
186
|
+
}
|
|
187
|
+
switch (String(status.phase || '').toLowerCase()) {
|
|
188
|
+
case 'degraded':
|
|
189
|
+
case 'error':
|
|
190
|
+
case 'failed':
|
|
191
|
+
case 'aborted':
|
|
192
|
+
return merge(base, 'stalled', false, 'Rollout stalled', status.message || 'The Rollout controller reported a failed revision')
|
|
193
|
+
case 'paused':
|
|
194
|
+
return merge(base, 'paused', true, 'Rollout paused', detail)
|
|
195
|
+
case 'progressing':
|
|
196
|
+
return updated === 0
|
|
197
|
+
? merge(base, 'waiting', true, 'Waiting for new revision', detail)
|
|
198
|
+
: merge(base, 'progressing', true, 'Rolling out', detail)
|
|
199
|
+
case 'healthy':
|
|
200
|
+
return merge(base, 'idle', false, 'Stable', `${available}/${desired} available`)
|
|
201
|
+
default:
|
|
202
|
+
return desired > 0 && (updated < desired || available < desired)
|
|
203
|
+
? merge(base, 'applying', true, 'Applying change', detail)
|
|
204
|
+
: merge(base, 'idle', false, 'Stable', status.message || '')
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function withUpdatedRevisionFailure(
|
|
209
|
+
rollout: WorkloadRolloutActivity,
|
|
210
|
+
pods?: WorkloadPodInfo[],
|
|
211
|
+
): WorkloadRolloutActivity {
|
|
212
|
+
if ((!rollout.active && rollout.phase !== 'stalled') || rollout.phase === 'applying' || !pods) return rollout
|
|
213
|
+
const failed = pods
|
|
214
|
+
.filter((pod) => pod.updatedRevision === true && pod.healthLevel === 'unhealthy')
|
|
215
|
+
.sort((a, b) => a.name.localeCompare(b.name))[0]
|
|
216
|
+
if (!failed) return rollout
|
|
217
|
+
const reason = failed.reason || failed.lastTerminationReason || failed.phase || 'Pod failed'
|
|
218
|
+
return merge(rollout, 'stalled', false, 'New revision cannot start', `${reason} · Pod ${failed.name}`)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function getArgoRolloutStepNumber(resource: any): number | null {
|
|
222
|
+
const steps = resource?.spec?.strategy?.canary?.steps || []
|
|
223
|
+
const currentIndex = resource?.status?.currentStepIndex
|
|
224
|
+
if (steps.length === 0 || typeof currentIndex !== 'number') return null
|
|
225
|
+
return Math.min(Math.max(currentIndex + 1, 1), steps.length)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function argoDetail(resource: any, updated: number, desired: number, available: number): string {
|
|
229
|
+
const stepNumber = getArgoRolloutStepNumber(resource)
|
|
230
|
+
const step = stepNumber === null ? '' : `Step ${stepNumber} · `
|
|
231
|
+
return `${step}${updated}/${desired} updated · ${available} available`
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function replicaDetail(updated: number, desired: number, available: number): string {
|
|
235
|
+
return `${updated}/${desired} updated · ${available} available`
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function activity(
|
|
239
|
+
phase: WorkloadRolloutPhase,
|
|
240
|
+
active: boolean,
|
|
241
|
+
manual: boolean,
|
|
242
|
+
label: string,
|
|
243
|
+
detail: string,
|
|
244
|
+
desired: number,
|
|
245
|
+
updated: number,
|
|
246
|
+
ready: number,
|
|
247
|
+
available: number,
|
|
248
|
+
): WorkloadRolloutActivity {
|
|
249
|
+
return { phase, active, manual, label, detail, desired, updated, ready, available }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function merge(
|
|
253
|
+
base: WorkloadRolloutActivity,
|
|
254
|
+
phase: WorkloadRolloutPhase,
|
|
255
|
+
active: boolean,
|
|
256
|
+
label: string,
|
|
257
|
+
detail: string,
|
|
258
|
+
): WorkloadRolloutActivity {
|
|
259
|
+
return { ...base, phase, active, manual: false, label, detail }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function mergeManual(
|
|
263
|
+
base: WorkloadRolloutActivity,
|
|
264
|
+
phase: WorkloadRolloutPhase,
|
|
265
|
+
active: boolean,
|
|
266
|
+
label: string,
|
|
267
|
+
detail: string,
|
|
268
|
+
): WorkloadRolloutActivity {
|
|
269
|
+
return { ...base, phase, active, manual: true, label, detail }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function rolloutActivityLevel(activity: WorkloadRolloutActivity): 'healthy' | 'neutral' | 'degraded' | 'unhealthy' {
|
|
273
|
+
switch (activity.phase) {
|
|
274
|
+
case 'stalled':
|
|
275
|
+
return 'unhealthy'
|
|
276
|
+
case 'paused':
|
|
277
|
+
return 'degraded'
|
|
278
|
+
case 'waiting':
|
|
279
|
+
return activity.manual ? 'degraded' : 'neutral'
|
|
280
|
+
case 'applying':
|
|
281
|
+
case 'progressing':
|
|
282
|
+
case 'partition-reached':
|
|
283
|
+
return 'neutral'
|
|
284
|
+
default:
|
|
285
|
+
return activity.label === 'Stable' ? 'healthy' : 'neutral'
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function rolloutActivityBadge(activity: WorkloadRolloutActivity): { text: string; color: string; level: 'healthy' | 'neutral' | 'degraded' | 'unhealthy' } {
|
|
290
|
+
const level = rolloutActivityLevel(activity)
|
|
291
|
+
return { text: activity.label, color: `status-${level}`, level }
|
|
292
|
+
}
|