@skyhook-io/k8s-ui 1.19.0 → 1.20.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 +9 -2
- package/src/components/applications/ApplicationDetail.tsx +8 -2
- package/src/components/cluster-switcher/ClusterSwitcher.test.tsx +54 -0
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +7 -3
- package/src/components/compare/ResourceCompareView.test.tsx +54 -0
- package/src/components/compare/ResourceCompareView.tsx +35 -12
- package/src/components/gitops/GitOpsDetailLayout.test.tsx +16 -0
- package/src/components/gitops/GitOpsDetailLayout.tsx +54 -7
- package/src/components/gitops/GitOpsHealthSourceNotice.tsx +1 -1
- package/src/components/gitops/GitOpsTableView.tsx +26 -7
- package/src/components/gitops/RollbackDialog.test.tsx +80 -0
- package/src/components/gitops/RollbackDialog.tsx +25 -6
- package/src/components/gitops/SyncOptionsDialog.render.test.tsx +374 -0
- package/src/components/gitops/SyncOptionsDialog.test.ts +54 -1
- package/src/components/gitops/SyncOptionsDialog.tsx +179 -34
- package/src/components/gitops/applicationset-row.test.ts +12 -0
- package/src/components/gitops/argo-radar-upgrade.test.tsx +369 -0
- package/src/components/gitops/detail-helpers.test.ts +8 -2
- package/src/components/gitops/detail-helpers.ts +9 -11
- package/src/components/gitops/index.ts +1 -1
- package/src/components/gitops/insights/GitOpsActivityInsightView.test.tsx +55 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +30 -12
- package/src/components/gitops/insights/GitOpsRollbackInsightView.test.tsx +73 -0
- package/src/components/gitops/insights/GitOpsSyncWindowInsightView.test.tsx +29 -0
- package/src/components/gitops/insights/insights-helpers.test.ts +37 -0
- package/src/components/gitops/insights/insights-helpers.ts +12 -0
- package/src/components/issues/ResourceIssuesSection.tsx +3 -1
- package/src/components/issues/diagnostic.ts +14 -1
- package/src/components/issues/severity.ts +14 -0
- package/src/components/kueue/AdmissionPath.tsx +223 -0
- package/src/components/kueue/QueueEvidence.tsx +148 -0
- package/src/components/kueue/constants.ts +1 -0
- package/src/components/kueue/eligibility.ts +43 -0
- package/src/components/kueue/flavors-tried.ts +79 -0
- package/src/components/kueue/helpers.test.ts +105 -0
- package/src/components/kueue/policy.ts +100 -0
- package/src/components/kueue/queue-detail.test.tsx +317 -0
- package/src/components/kueue/queue-status.ts +42 -0
- package/src/components/kueue/related-read.ts +48 -0
- package/src/components/kueue/testdata/kueue-v0.18-policy-enums.json +89 -0
- package/src/components/logs/StructuredLogLine.test.tsx +22 -0
- package/src/components/logs/StructuredLogLine.tsx +8 -3
- package/src/components/resources/KueueAdmissionSection.test.tsx +117 -0
- package/src/components/resources/KueueAdmissionSection.tsx +51 -12
- package/src/components/resources/KueuePendingPosition.test.tsx +97 -0
- package/src/components/resources/KueuePendingPosition.tsx +91 -0
- package/src/components/resources/ResourcesSidebar.test.tsx +8 -0
- package/src/components/resources/ResourcesSidebar.tsx +3 -2
- package/src/components/resources/ResourcesView.crd.test.tsx +28 -0
- package/src/components/resources/ResourcesView.node-pods.test.tsx +25 -0
- package/src/components/resources/ResourcesView.strimzi.test.tsx +37 -0
- package/src/components/resources/ResourcesView.tsx +50 -3
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/generic-status.ts +7 -0
- package/src/components/resources/get-pod-phase-display.test.ts +70 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.test.tsx +54 -1
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +7 -8
- package/src/components/resources/renderers/CRDSchemaBrowser.test.tsx +105 -0
- package/src/components/resources/renderers/CRDSchemaBrowser.tsx +134 -0
- package/src/components/resources/renderers/CustomResourceDefinitionRenderer.test.tsx +106 -0
- package/src/components/resources/renderers/CustomResourceDefinitionRenderer.tsx +152 -0
- package/src/components/resources/renderers/JobRenderer.test.tsx +35 -0
- package/src/components/resources/renderers/JobRenderer.tsx +7 -2
- package/src/components/resources/renderers/KueueQueueRenderers.test.tsx +5 -5
- package/src/components/resources/renderers/KueueQueueRenderers.tsx +250 -170
- package/src/components/resources/renderers/KueueQueueWorkloads.test.tsx +189 -0
- package/src/components/resources/renderers/KueueQueueWorkloads.tsx +460 -0
- package/src/components/resources/renderers/KueueWorkloadRenderer.test.tsx +1 -1
- package/src/components/resources/renderers/KueueWorkloadRenderer.tsx +23 -52
- package/src/components/resources/renderers/OpenTelemetryRenderers.tsx +11 -6
- package/src/components/resources/renderers/RayClusterRenderer.test.tsx +39 -0
- package/src/components/resources/renderers/RayClusterRenderer.tsx +23 -5
- package/src/components/resources/renderers/ServiceAccountRenderer.test.tsx +83 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +15 -10
- package/src/components/resources/renderers/StrimziConnectorRenderer.tsx +61 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +9 -3
- package/src/components/resources/renderers/crd-cells.test.tsx +30 -0
- package/src/components/resources/renderers/crd-cells.tsx +55 -0
- package/src/components/resources/renderers/crd-schema-parts.test.tsx +59 -0
- package/src/components/resources/renderers/crd-schema-parts.tsx +121 -0
- package/src/components/resources/renderers/index.ts +6 -0
- package/src/components/resources/renderers/kueue-cells.tsx +5 -0
- package/src/components/resources/resource-utils-argo.test.ts +94 -1
- package/src/components/resources/resource-utils-argo.ts +84 -5
- package/src/components/resources/resource-utils-crd.test.ts +32 -0
- package/src/components/resources/resource-utils-crd.ts +26 -0
- package/src/components/resources/resource-utils-gpu-ecosystem.test.ts +12 -0
- package/src/components/resources/resource-utils-kueue-admission.test.ts +26 -0
- package/src/components/resources/resource-utils-kueue.test.ts +15 -0
- package/src/components/resources/resource-utils-kueue.ts +137 -4
- package/src/components/resources/resource-utils-opentelemetry.test.tsx +9 -1
- package/src/components/resources/resource-utils-opentelemetry.ts +1 -1
- package/src/components/resources/resource-utils-ray.test.ts +42 -1
- package/src/components/resources/resource-utils-ray.ts +38 -0
- package/src/components/resources/resource-utils-strimzi.test.tsx +118 -0
- package/src/components/resources/resource-utils-strimzi.ts +35 -0
- package/src/components/resources/resource-utils.ts +45 -1
- package/src/components/shared/ResourceActionsBar.tsx +83 -25
- package/src/components/shared/ResourceRendererDispatch.test.tsx +37 -3
- package/src/components/shared/ResourceRendererDispatch.tsx +40 -7
- package/src/components/topology/K8sResourceNode.test.ts +14 -0
- package/src/components/topology/K8sResourceNode.tsx +18 -1
- package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
- package/src/components/topology/TopologyGraph.tsx +96 -94
- package/src/components/topology/layout.ts +3 -1
- package/src/components/topology/pod-group-expansion.test.ts +38 -0
- package/src/components/ui/Badge.tsx +1 -0
- package/src/components/ui/RadarUpgradeNote.tsx +8 -0
- package/src/components/ui/drawer-components.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/workload/PodList.tsx +35 -9
- package/src/components/workload/WorkloadView.spec.test.tsx +41 -1
- package/src/components/workload/WorkloadView.tsx +61 -8
- package/src/components/workload/workload-logs-availability.test.ts +7 -0
- package/src/types/core.ts +40 -0
- package/src/types/gitops-insights.ts +11 -2
- package/src/types/gitops-tree.ts +2 -2
- package/src/types/kueue-queues.ts +84 -0
- package/src/types/read-coverage.ts +2 -1
- package/src/types/scheduling.ts +72 -0
- package/src/utils/animation.test.ts +38 -0
- package/src/utils/animation.ts +34 -0
- package/src/utils/api-resources-crd.test.ts +11 -0
- package/src/utils/api-resources.ts +5 -1
- package/src/utils/applications.test.ts +15 -1
- package/src/utils/applications.ts +26 -1
- package/src/utils/crd-schema.test.ts +107 -0
- package/src/utils/crd-schema.ts +201 -0
- package/src/utils/format.test.ts +37 -1
- package/src/utils/format.ts +28 -80
- package/src/utils/gitops-owner.test.ts +28 -2
- package/src/utils/gitops-owner.ts +19 -0
- package/src/utils/kueue-queue-workloads.ts +19 -0
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/topology-neighborhood.test.ts +90 -1
- package/src/utils/topology-neighborhood.ts +55 -3
- package/src/utils/workload-history-notes.test.ts +76 -0
- package/src/utils/workload-history-notes.ts +93 -0
|
@@ -1,99 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { Gauge, Link2, ListChecks, Scale } from 'lucide-react'
|
|
3
3
|
import {
|
|
4
4
|
AlertBanner,
|
|
5
5
|
ConditionsSection,
|
|
6
6
|
LabelSelectorDisplay,
|
|
7
|
-
Property,
|
|
8
|
-
PropertyList,
|
|
9
7
|
ResourceLink,
|
|
10
8
|
Section,
|
|
11
9
|
type ConditionTone,
|
|
12
10
|
} from '../../ui/drawer-components'
|
|
11
|
+
import { FactGrid, FactRow, FactSource, FactValue } from '../../facts'
|
|
13
12
|
import {
|
|
14
13
|
getClusterQueueCohort,
|
|
15
14
|
getClusterQueueStatus,
|
|
16
|
-
getLocalQueueStatus,
|
|
17
15
|
getKueueQueueCondition,
|
|
18
16
|
getKueueQueueQuotaRows,
|
|
19
17
|
isKueueConditionStale,
|
|
18
|
+
isKueueStopOnly,
|
|
20
19
|
type KueueQuotaRow,
|
|
21
20
|
} from '../resource-utils-kueue'
|
|
21
|
+
import { describeKueuePolicy, type KueuePolicyField } from '../../kueue/policy'
|
|
22
|
+
import { isDenied, unreadReason, type RelatedRead } from '../../kueue/related-read'
|
|
23
|
+
import { localQueueInactiveBecauseParentStopped, localQueueStatusWithParent } from '../../kueue/queue-status'
|
|
24
|
+
import {
|
|
25
|
+
AdmissionCheckRow,
|
|
26
|
+
ClusterQueueReadSummary,
|
|
27
|
+
ConfiguredStop,
|
|
28
|
+
namespaceEligibilityFact,
|
|
29
|
+
QueueCounts,
|
|
30
|
+
ReadState,
|
|
31
|
+
ReportedActive,
|
|
32
|
+
type Navigate,
|
|
33
|
+
} from '../../kueue/QueueEvidence'
|
|
34
|
+
import { KUEUE_GROUP } from '../../kueue/constants'
|
|
22
35
|
|
|
23
|
-
const KUEUE_GROUP = 'kueue.x-k8s.io'
|
|
24
36
|
interface QueueRendererProps {
|
|
25
37
|
data: any
|
|
26
|
-
onNavigate?:
|
|
38
|
+
onNavigate?: Navigate
|
|
39
|
+
workloads?: ReactNode
|
|
27
40
|
}
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const counts = [
|
|
37
|
-
['Pending', data.status?.pendingWorkloads],
|
|
38
|
-
['Reserving quota', data.status?.reservingWorkloads],
|
|
39
|
-
['Admitted', data.status?.admittedWorkloads],
|
|
40
|
-
] as const
|
|
41
|
-
return (
|
|
42
|
-
<>
|
|
43
|
-
{stale && (
|
|
44
|
-
<AlertBanner
|
|
45
|
-
variant="info"
|
|
46
|
-
title="Reported state describes an earlier generation"
|
|
47
|
-
message={`Active condition: generation ${condition.observedGeneration}; queue: generation ${data.metadata.generation}. The reported state may not reflect the current specification.`}
|
|
48
|
-
/>
|
|
49
|
-
)}
|
|
50
|
-
<Section title="Queue Status" icon={Gauge}>
|
|
51
|
-
<PropertyList>
|
|
52
|
-
<Property label="Reported State" value={<Badge colorClass={badge.color}>{badge.text}</Badge>} />
|
|
53
|
-
<Property label="Stop Policy" value={stop} />
|
|
54
|
-
{counts.some(([, count]) => count != null) &&
|
|
55
|
-
counts.map(([label, count]) => <Property key={label} label={label} value={quantity(count)} />)}
|
|
56
|
-
</PropertyList>
|
|
57
|
-
{condition?.message && (
|
|
58
|
-
<p className="mt-2 whitespace-pre-wrap break-words text-sm text-theme-text-secondary">{condition.message}</p>
|
|
59
|
-
)}
|
|
60
|
-
{!condition && counts.some(([, count]) => count != null) && (
|
|
61
|
-
<p className="mt-2 text-sm text-theme-text-secondary">No Active condition reported.</p>
|
|
62
|
-
)}
|
|
63
|
-
{counts.every(([, count]) => count == null) ? (
|
|
64
|
-
<p className="mt-2 text-xs text-theme-text-tertiary">
|
|
65
|
-
{condition ? 'Workload counts have not been reported.' : 'Controller status has not been reported.'}
|
|
66
|
-
</p>
|
|
67
|
-
) : (
|
|
68
|
-
<p className="mt-2 text-xs text-theme-text-tertiary">
|
|
69
|
-
Reserving counts include admitted Workloads; these counts are not additive. Admission does not establish
|
|
70
|
-
that Pods are running.
|
|
71
|
-
</p>
|
|
72
|
-
)}
|
|
73
|
-
{(stop === 'Hold' || stop === 'HoldAndDrain') && (
|
|
74
|
-
<p className="mt-2 text-sm text-theme-text-secondary">
|
|
75
|
-
Configured to stop new reservations and cancel reservations for Workloads awaiting admission.{' '}
|
|
76
|
-
{stop === 'HoldAndDrain'
|
|
77
|
-
? 'Admitted Workloads are to be evicted.'
|
|
78
|
-
: 'Already admitted Workloads may continue.'}{' '}
|
|
79
|
-
The reported state above shows the controller observation.
|
|
80
|
-
</p>
|
|
81
|
-
)}
|
|
82
|
-
</Section>
|
|
83
|
-
</>
|
|
84
|
-
)
|
|
42
|
+
/**
|
|
43
|
+
* Related objects a host read as the viewer. Absent: the host doesn't read
|
|
44
|
+
* them, and names are shown without read state.
|
|
45
|
+
*/
|
|
46
|
+
export interface LocalQueueReads {
|
|
47
|
+
clusterQueue?: RelatedRead
|
|
48
|
+
namespace?: RelatedRead
|
|
85
49
|
}
|
|
86
50
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return <ConditionsSection conditions={data.status?.conditions} getConditionTone={tone} defaultExpanded />
|
|
51
|
+
export interface ClusterQueueReads {
|
|
52
|
+
flavors?: Record<string, RelatedRead>
|
|
53
|
+
admissionChecks?: Record<string, RelatedRead>
|
|
54
|
+
/** Other ClusterQueues naming the same cohort, from Radar's ClusterQueue list. */
|
|
55
|
+
cohortMembers?: RelatedRead<string[]>
|
|
56
|
+
/** Whether the ClusterQueue list the members came from covered every ClusterQueue. */
|
|
57
|
+
cohortMembersCompleteness?: 'complete' | 'partial' | 'unknown'
|
|
95
58
|
}
|
|
96
59
|
|
|
60
|
+
const quantity = (value: string | number | undefined | null) => (value == null ? 'Not reported' : String(value))
|
|
61
|
+
|
|
97
62
|
function QuotaAccounting({ data, clusterQueue = false, onNavigate }: QueueRendererProps & { clusterQueue?: boolean }) {
|
|
98
63
|
const rows = getKueueQueueQuotaRows(data, clusterQueue)
|
|
99
64
|
const flavors = new Map<string, KueueQuotaRow[]>()
|
|
@@ -104,7 +69,7 @@ function QuotaAccounting({ data, clusterQueue = false, onNavigate }: QueueRender
|
|
|
104
69
|
}
|
|
105
70
|
const hasCohort = clusterQueue && getClusterQueueCohort(data) !== '-'
|
|
106
71
|
return (
|
|
107
|
-
<Section title={clusterQueue ? 'Quota by Flavor' : 'Quota Accounting'}>
|
|
72
|
+
<Section variant="card" title={clusterQueue ? 'Quota by Flavor' : 'Quota Accounting'} icon={Gauge}>
|
|
108
73
|
<p className="mb-3 text-xs text-theme-text-tertiary">
|
|
109
74
|
Reserved and admitted usage are quota accounting from resource requests, not measured utilization. Admitted
|
|
110
75
|
usage is included in reservations.
|
|
@@ -184,116 +149,231 @@ function QuotaRow({ row, clusterQueue, hasCohort }: { row: KueueQuotaRow; cluste
|
|
|
184
149
|
)
|
|
185
150
|
}
|
|
186
151
|
|
|
187
|
-
|
|
188
|
-
|
|
152
|
+
|
|
153
|
+
function StaleBanner({ data }: { data: any }) {
|
|
154
|
+
const condition = getKueueQueueCondition(data)
|
|
155
|
+
if (!isKueueConditionStale(condition, data.metadata?.generation)) return null
|
|
189
156
|
return (
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
157
|
+
<AlertBanner
|
|
158
|
+
variant="info"
|
|
159
|
+
className="mb-0"
|
|
160
|
+
title="Reported state describes an earlier generation"
|
|
161
|
+
message={`Active condition: generation ${condition.observedGeneration}; queue: generation ${data.metadata.generation}. The reported state may not reflect the current specification.`}
|
|
162
|
+
/>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function QueueConditions({ data, clusterQueue = false, parentStopped = false }: { data: any; clusterQueue?: boolean; parentStopped?: boolean }) {
|
|
167
|
+
const tone = (condition: any): ConditionTone => {
|
|
168
|
+
if (isKueueConditionStale(condition, data.metadata?.generation) || condition.type !== 'Active') return 'unknown'
|
|
169
|
+
if (condition.status === 'True') return 'ok'
|
|
170
|
+
if (condition.status === 'False') return isKueueStopOnly(condition, clusterQueue) || (parentStopped && condition.reason === 'ClusterQueueIsInactive') ? 'unknown' : 'warning'
|
|
171
|
+
return 'unknown'
|
|
172
|
+
}
|
|
173
|
+
return <ConditionsSection variant="card" conditions={data.status?.conditions} getConditionTone={tone} defaultExpanded />
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Reported (left) and configured (right) side by side once the content is wide enough. */
|
|
177
|
+
function Situation({ reported, configured }: { reported: ReactNode; configured: ReactNode }) {
|
|
178
|
+
return (
|
|
179
|
+
<div className="grid min-w-0 items-start gap-4 @min-[760px]/queue:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]">
|
|
180
|
+
<Section variant="card" title="Reported by controller">{reported}</Section>
|
|
181
|
+
<Section variant="card" title="Configured">{configured}</Section>
|
|
182
|
+
</div>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function LocalQueueRenderer({ data, onNavigate, reads, workloads }: QueueRendererProps & { reads?: LocalQueueReads }) {
|
|
187
|
+
const parentName: string | undefined = data.spec?.clusterQueue
|
|
188
|
+
const namespace: string = data.metadata?.namespace ?? ''
|
|
189
|
+
const parentStopped = localQueueInactiveBecauseParentStopped(data, reads?.clusterQueue)
|
|
190
|
+
const reportsParentInactive = getKueueQueueCondition(data)?.reason === 'ClusterQueueIsInactive'
|
|
191
|
+
const eligibility = parentName && reads ? namespaceEligibilityFact({ clusterQueue: parentName, parent: reads.clusterQueue, namespaceRead: reads.namespace }) : undefined
|
|
192
|
+
return (
|
|
193
|
+
<div className="@container/queue space-y-4">
|
|
194
|
+
<StaleBanner data={data} />
|
|
195
|
+
<Situation
|
|
196
|
+
reported={<ReportedActive data={data} status={localQueueStatusWithParent(data, reads?.clusterQueue)}>
|
|
197
|
+
{parentStopped && <p className="text-xs text-theme-text-secondary">ClusterQueue {parentName} reports Stopped (read from the ClusterQueue), so this LocalQueue's inactivity follows an intentional stop.</p>}
|
|
198
|
+
{reportsParentInactive && !parentStopped && reads?.clusterQueue?.state !== 'loading' && reads?.clusterQueue?.state !== 'not_found' && <p className="text-xs text-theme-text-secondary">{isDenied(reads?.clusterQueue)
|
|
199
|
+
? `The cause is on ClusterQueue ${parentName}, which you can’t read; a cluster administrator can check it.`
|
|
200
|
+
: reads?.clusterQueue?.state === 'found' && getKueueQueueCondition(reads.clusterQueue.object)?.status === 'True' && !isKueueConditionStale(getKueueQueueCondition(reads.clusterQueue.object), reads.clusterQueue.object?.metadata?.generation)
|
|
201
|
+
? `ClusterQueue ${parentName} now reports Active; this LocalQueue’s report hasn’t caught up.`
|
|
202
|
+
: 'Inspect the ClusterQueue for the cause of its reported inactivity.'}</p>}
|
|
203
|
+
<div className="border-t border-theme-border pt-3"><QueueCounts data={data} kind="LocalQueue" /></div>
|
|
204
|
+
</ReportedActive>}
|
|
205
|
+
configured={<div className="space-y-3">
|
|
206
|
+
<ConfiguredStop data={data} />
|
|
207
|
+
<div className="text-sm">
|
|
208
|
+
<span className="text-theme-text-tertiary">ClusterQueue </span>
|
|
209
|
+
{parentName
|
|
210
|
+
? <span className="break-all [&_button]:text-left"><ResourceLink name={parentName} kind="clusterqueues" group={KUEUE_GROUP} onNavigate={onNavigate} /></span>
|
|
211
|
+
: <span className="text-theme-text-secondary">No ClusterQueue reference reported.</span>}
|
|
212
|
+
</div>
|
|
213
|
+
</div>}
|
|
214
|
+
/>
|
|
215
|
+
{parentName && reads && (
|
|
216
|
+
<Section variant="card" title="ClusterQueue" icon={Link2}>
|
|
217
|
+
<div className="space-y-3">
|
|
218
|
+
<ClusterQueueReadSummary name={parentName} read={reads.clusterQueue} onNavigate={onNavigate} />
|
|
219
|
+
{reads.clusterQueue?.state === 'not_found' && <p className="text-sm text-theme-text-secondary">This LocalQueue points at a ClusterQueue that doesn't exist; Workloads submitted here can't be admitted until it does.</p>}
|
|
220
|
+
{eligibility && <FactGrid>
|
|
221
|
+
<FactRow label={`Namespace ${namespace}`}><FactValue fact={eligibility} /><FactSource fact={eligibility} /></FactRow>
|
|
222
|
+
</FactGrid>}
|
|
223
|
+
</div>
|
|
224
|
+
</Section>
|
|
225
|
+
)}
|
|
209
226
|
<QuotaAccounting data={data} onNavigate={onNavigate} />
|
|
210
|
-
|
|
211
|
-
|
|
227
|
+
{workloads}
|
|
228
|
+
<QueueConditions data={data} parentStopped={parentStopped} />
|
|
229
|
+
</div>
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function PolicyRow({ data, label, field, value }: { data: any; label: string; field: KueuePolicyField; value: unknown }) {
|
|
234
|
+
if (value == null) return null
|
|
235
|
+
const policy = describeKueuePolicy(data.apiVersion, field, value)
|
|
236
|
+
return (
|
|
237
|
+
<FactRow label={label}>
|
|
238
|
+
<code className="text-theme-text-primary">{policy.raw}</code>
|
|
239
|
+
<div className="mt-0.5 text-xs text-theme-text-secondary">{policy.meaning ?? `Radar has no description of this value for ${data.apiVersion}.`}</div>
|
|
240
|
+
</FactRow>
|
|
212
241
|
)
|
|
213
242
|
}
|
|
214
243
|
|
|
215
|
-
|
|
244
|
+
function AdmissionPolicy({ data }: { data: any }) {
|
|
216
245
|
const spec = data.spec ?? {}
|
|
217
246
|
const cohort = getClusterQueueCohort(data)
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
247
|
+
const inCohort = cohort !== '-'
|
|
248
|
+
return (
|
|
249
|
+
<Section variant="card" title="Admission Policy" icon={Scale}>
|
|
250
|
+
<FactGrid>
|
|
251
|
+
<FactRow label="Eligible namespaces">
|
|
252
|
+
<LabelSelectorDisplay selector={spec.namespaceSelector} emptyText={spec.namespaceSelector == null ? 'No namespaces eligible (namespaceSelector not set)' : 'All namespaces ({})'} />
|
|
253
|
+
</FactRow>
|
|
254
|
+
<PolicyRow data={data} label="Queueing strategy" field="queueingStrategy" value={spec.queueingStrategy} />
|
|
255
|
+
<PolicyRow data={data} label="Preemption within queue" field="preemption.withinClusterQueue" value={spec.preemption?.withinClusterQueue} />
|
|
256
|
+
{inCohort && <PolicyRow data={data} label="Reclaim within cohort" field="preemption.reclaimWithinCohort" value={spec.preemption?.reclaimWithinCohort} />}
|
|
257
|
+
{inCohort && <PolicyRow data={data} label="Borrow with preemption" field="preemption.borrowWithinCohort.policy" value={spec.preemption?.borrowWithinCohort?.policy} />}
|
|
258
|
+
{inCohort && spec.preemption?.borrowWithinCohort?.maxPriorityThreshold != null && (
|
|
259
|
+
<FactRow label="Preemption priority threshold">{String(spec.preemption.borrowWithinCohort.maxPriorityThreshold)}</FactRow>
|
|
260
|
+
)}
|
|
261
|
+
{inCohort && <PolicyRow data={data} label="Flavor when borrowing" field="flavorFungibility.whenCanBorrow" value={spec.flavorFungibility?.whenCanBorrow} />}
|
|
262
|
+
<PolicyRow data={data} label="Flavor when preempting" field="flavorFungibility.whenCanPreempt" value={spec.flavorFungibility?.whenCanPreempt} />
|
|
263
|
+
<PolicyRow data={data} label="Flavor preference" field="flavorFungibility.preference" value={spec.flavorFungibility?.preference} />
|
|
264
|
+
<PolicyRow data={data} label="Admission fair sharing" field="admissionScope.admissionMode" value={spec.admissionScope?.admissionMode} />
|
|
265
|
+
{spec.fairSharing?.weight != null && <FactRow label="Fair sharing weight">{String(spec.fairSharing.weight)}</FactRow>}
|
|
266
|
+
{data.status?.fairSharing?.weightedShare != null && <FactRow label="Reported weighted share">{String(data.status.fairSharing.weightedShare)}</FactRow>}
|
|
267
|
+
</FactGrid>
|
|
268
|
+
<p className="mt-3 text-xs text-theme-text-tertiary">Values include any defaults the API server filled in.</p>
|
|
269
|
+
{inCohort && (
|
|
270
|
+
<p className="mt-3 text-xs text-theme-text-tertiary">
|
|
271
|
+
Absent an explicit limit, borrowing has no configured cap and all nominal quota may be lent. Borrowing
|
|
272
|
+
depends on available cohort quota; a borrowing limit does not guarantee capacity. Reported borrowed amounts
|
|
273
|
+
are already included in their totals.
|
|
274
|
+
</p>
|
|
275
|
+
)}
|
|
276
|
+
</Section>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function clusterQueueChecks(data: any): { name: string; onFlavors?: string[] }[] {
|
|
281
|
+
const spec = data.spec ?? {}
|
|
282
|
+
return [
|
|
283
|
+
...(data.apiVersion === 'kueue.x-k8s.io/v1beta1' ? (spec.admissionChecks ?? []).map((name: string) => ({ name })) : []),
|
|
222
284
|
...(spec.admissionChecksStrategy?.admissionChecks ?? []),
|
|
223
285
|
]
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Flavor names per resource group, in configured preference order. */
|
|
289
|
+
function clusterQueueFlavorGroups(data: any): { resources: string[]; flavors: string[] }[] {
|
|
290
|
+
return (data.spec?.resourceGroups ?? []).map((group: any) => ({
|
|
291
|
+
resources: group.coveredResources ?? [],
|
|
292
|
+
flavors: (group.flavors ?? []).map((flavor: any) => flavor?.name).filter(Boolean),
|
|
293
|
+
}))
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function clusterQueueRelatedNames(data: any): { flavors: string[]; admissionChecks: string[] } {
|
|
297
|
+
return {
|
|
298
|
+
flavors: [...new Set(clusterQueueFlavorGroups(data).flatMap(group => group.flavors))],
|
|
299
|
+
admissionChecks: [...new Set(clusterQueueChecks(data).map(check => check.name))],
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function RelatedObjects({ data, reads, onNavigate }: QueueRendererProps & { reads?: ClusterQueueReads }) {
|
|
304
|
+
const cohort = getClusterQueueCohort(data)
|
|
305
|
+
const groups = clusterQueueFlavorGroups(data)
|
|
306
|
+
const checks = clusterQueueChecks(data)
|
|
307
|
+
const members = reads?.cohortMembers
|
|
308
|
+
const heading = 'mb-2 text-xs font-medium uppercase tracking-wider text-theme-text-secondary'
|
|
224
309
|
return (
|
|
225
|
-
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
{spec.preemption?.withinClusterQueue != null && (
|
|
242
|
-
<Property label="Preemption within Queue" value={spec.preemption.withinClusterQueue} />
|
|
243
|
-
)}
|
|
244
|
-
{cohort !== '-' && spec.preemption?.reclaimWithinCohort != null && (
|
|
245
|
-
<Property label="Reclaim within Cohort" value={spec.preemption.reclaimWithinCohort} />
|
|
246
|
-
)}
|
|
247
|
-
{cohort !== '-' && spec.preemption?.borrowWithinCohort?.policy != null && (
|
|
248
|
-
<Property label="Borrow with Preemption" value={spec.preemption.borrowWithinCohort.policy} />
|
|
249
|
-
)}
|
|
250
|
-
{cohort !== '-' && spec.preemption?.borrowWithinCohort?.maxPriorityThreshold != null && (
|
|
251
|
-
<Property
|
|
252
|
-
label="Preemption Priority Threshold"
|
|
253
|
-
value={spec.preemption.borrowWithinCohort.maxPriorityThreshold}
|
|
254
|
-
/>
|
|
255
|
-
)}
|
|
256
|
-
{cohort !== '-' && spec.flavorFungibility?.whenCanBorrow != null && (
|
|
257
|
-
<Property label="Flavor when Borrowing" value={spec.flavorFungibility.whenCanBorrow} />
|
|
258
|
-
)}
|
|
259
|
-
{spec.flavorFungibility?.whenCanPreempt != null && (
|
|
260
|
-
<Property label="Flavor when Preempting" value={spec.flavorFungibility.whenCanPreempt} />
|
|
261
|
-
)}
|
|
262
|
-
{spec.flavorFungibility?.preference != null && (
|
|
263
|
-
<Property label="Flavor Preference" value={spec.flavorFungibility.preference} />
|
|
264
|
-
)}
|
|
265
|
-
{spec.fairSharing?.weight != null && (
|
|
266
|
-
<Property label="Fair Sharing Weight" value={String(spec.fairSharing.weight)} />
|
|
310
|
+
<Section variant="card" title="Related Objects" icon={ListChecks}>
|
|
311
|
+
<div className="space-y-4">
|
|
312
|
+
<div>
|
|
313
|
+
<h4 className={heading}>Cohort</h4>
|
|
314
|
+
{cohort === '-' ? <p className="text-sm text-theme-text-secondary">None: this ClusterQueue neither lends nor borrows quota.</p> : (
|
|
315
|
+
<div className="space-y-1 text-sm">
|
|
316
|
+
<code className="break-all text-theme-text-primary">{cohort}</code>
|
|
317
|
+
{members && <p className="text-xs text-theme-text-secondary">
|
|
318
|
+
{members.state === 'found'
|
|
319
|
+
? members.object.length > 0 ? <>Other ClusterQueues naming this cohort: {members.object.map((name, index) => <span key={name} className="[&_button]:text-left">{index > 0 && ', '}<ResourceLink name={name} kind="clusterqueues" group={KUEUE_GROUP} onNavigate={onNavigate} /></span>)}</> : 'No other ClusterQueue names this cohort.'
|
|
320
|
+
: `Other members not read: ${unreadReason(members)}.`}
|
|
321
|
+
{members.state === 'found' && reads?.cohortMembersCompleteness === 'partial' && ' Radar’s ClusterQueue list was incomplete, so there may be more.'}
|
|
322
|
+
{members.state === 'found' && reads?.cohortMembersCompleteness === 'unknown' && ' This Radar doesn’t say whether its ClusterQueue list is complete.'}
|
|
323
|
+
</p>}
|
|
324
|
+
<p className="text-xs text-theme-text-tertiary">A cohort exists through the ClusterQueues that name it. Radar doesn't read Cohort objects, so a parent cohort or cohort-level quota isn't shown here.</p>
|
|
325
|
+
</div>
|
|
267
326
|
)}
|
|
268
|
-
|
|
269
|
-
|
|
327
|
+
</div>
|
|
328
|
+
<div>
|
|
329
|
+
<h4 className={heading}>Resource flavors, in configured preference order</h4>
|
|
330
|
+
{groups.length === 0 ? <p className="text-sm text-theme-text-secondary">No resource groups configured.</p> : (
|
|
331
|
+
<div className="space-y-3">
|
|
332
|
+
{groups.map((group, index) => (
|
|
333
|
+
<div key={index} className="space-y-1">
|
|
334
|
+
{groups.length > 1 && <p className="text-xs text-theme-text-tertiary">{group.resources.length > 0 ? group.resources.join(', ') : `Resource group ${index + 1}`}</p>}
|
|
335
|
+
<ol className="space-y-1">
|
|
336
|
+
{group.flavors.map((name, position) => (
|
|
337
|
+
<li key={name} className="flex flex-wrap items-center gap-2 break-all text-sm [&_button]:text-left">
|
|
338
|
+
<span className="w-4 shrink-0 text-right text-xs text-theme-text-tertiary">{position + 1}.</span>
|
|
339
|
+
<ResourceLink name={name} kind="resourceflavors" group={KUEUE_GROUP} onNavigate={onNavigate} />
|
|
340
|
+
<ReadState read={reads?.flavors?.[name]} />
|
|
341
|
+
</li>
|
|
342
|
+
))}
|
|
343
|
+
</ol>
|
|
344
|
+
</div>
|
|
345
|
+
))}
|
|
346
|
+
</div>
|
|
270
347
|
)}
|
|
271
|
-
</
|
|
272
|
-
{
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
)}
|
|
279
|
-
</Section>
|
|
280
|
-
{checks.length > 0 && (
|
|
281
|
-
<Section title="Required Admission Checks">
|
|
282
|
-
<div className="space-y-3">
|
|
283
|
-
{checks.map((check, index) => (
|
|
284
|
-
<div key={`${check.name}-${index}`} className="text-sm">
|
|
285
|
-
<ResourceLink name={check.name} kind="admissionchecks" group={KUEUE_GROUP} onNavigate={onNavigate} />
|
|
286
|
-
<p className="mt-1 break-words text-xs text-theme-text-secondary">
|
|
287
|
-
{check.onFlavors?.length
|
|
288
|
-
? `Applies to flavors: ${check.onFlavors.join(', ')}`
|
|
289
|
-
: 'Applies to all flavors'}
|
|
290
|
-
</p>
|
|
291
|
-
</div>
|
|
292
|
-
))}
|
|
348
|
+
</div>
|
|
349
|
+
{checks.length > 0 && (
|
|
350
|
+
<div>
|
|
351
|
+
<h4 className={heading}>Required admission checks</h4>
|
|
352
|
+
<div className="space-y-3">
|
|
353
|
+
{checks.map((check, index) => <AdmissionCheckRow key={`${check.name}-${index}`} name={check.name} onFlavors={check.onFlavors} read={reads?.admissionChecks?.[check.name]} onNavigate={onNavigate} />)}
|
|
354
|
+
</div>
|
|
293
355
|
</div>
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
356
|
+
)}
|
|
357
|
+
</div>
|
|
358
|
+
</Section>
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function ClusterQueueRenderer({ data, onNavigate, reads, workloads }: QueueRendererProps & { reads?: ClusterQueueReads }) {
|
|
363
|
+
return (
|
|
364
|
+
<div className="@container/queue space-y-4">
|
|
365
|
+
<StaleBanner data={data} />
|
|
366
|
+
<Situation
|
|
367
|
+
reported={<ReportedActive data={data} status={getClusterQueueStatus(data)}>
|
|
368
|
+
<div className="border-t border-theme-border pt-3"><QueueCounts data={data} kind="ClusterQueue" /></div>
|
|
369
|
+
</ReportedActive>}
|
|
370
|
+
configured={<ConfiguredStop data={data} />}
|
|
371
|
+
/>
|
|
372
|
+
<RelatedObjects data={data} reads={reads} onNavigate={onNavigate} />
|
|
373
|
+
<QuotaAccounting data={data} clusterQueue onNavigate={onNavigate} />
|
|
374
|
+
{workloads}
|
|
375
|
+
<AdmissionPolicy data={data} />
|
|
376
|
+
<QueueConditions data={data} clusterQueue />
|
|
377
|
+
</div>
|
|
298
378
|
)
|
|
299
379
|
}
|