@skyhook-io/k8s-ui 1.14.5 → 1.14.7
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 +5 -5
- package/src/components/applications/ApplicationDetail.tsx +36 -4
- package/src/components/applications/ApplicationsView.tsx +2 -1
- package/src/components/audit/AuditAlerts.tsx +35 -37
- package/src/components/audit/AuditCard.tsx +18 -7
- package/src/components/audit/AuditCoveragePresentation.test.tsx +45 -0
- package/src/components/audit/AuditFindingsTable.tsx +23 -27
- package/src/components/charts/AreaChart.test.tsx +13 -0
- package/src/components/charts/AreaChart.tsx +28 -25
- package/src/components/charts/PrometheusChartsView.tsx +41 -10
- package/src/components/charts/axis.test.ts +16 -0
- package/src/components/charts/axis.ts +3 -1
- package/src/components/charts/format.ts +4 -0
- package/src/components/charts/nearestSample.test.ts +31 -0
- package/src/components/charts/nearestSample.ts +15 -0
- package/src/components/checks/ChecksView.tsx +76 -28
- package/src/components/gitops/GitOpsDetailLayout.tsx +25 -9
- package/src/components/gitops/GitOpsHealthSourceNotice.tsx +141 -0
- package/src/components/gitops/health-provenance.test.ts +54 -0
- package/src/components/gitops/health-provenance.ts +24 -0
- package/src/components/gitops/index.ts +2 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +40 -21
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +59 -8
- package/src/components/gitops/tree/merge.test.ts +34 -0
- package/src/components/gitops/tree/merge.ts +7 -0
- package/src/components/issues/IssuesView.tsx +35 -54
- package/src/components/logs/LogCore.tsx +40 -9
- package/src/components/resources/ResourcesSidebar.tsx +66 -78
- package/src/components/resources/ResourcesView.tsx +67 -2
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/renderers/AnalysisTemplateRenderer.tsx +150 -0
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +21 -15
- package/src/components/resources/renderers/CompositionRenderer.tsx +30 -10
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +16 -12
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +8 -1
- package/src/components/resources/renderers/ExperimentRenderer.tsx +179 -0
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +10 -4
- package/src/components/resources/renderers/GenericRenderer.tsx +13 -7
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +6 -4
- package/src/components/resources/renderers/LimitRangeRenderer.test.tsx +156 -0
- package/src/components/resources/renderers/LimitRangeRenderer.tsx +277 -0
- package/src/components/resources/renderers/NamespaceRenderer.tsx +24 -1
- package/src/components/resources/renderers/NetworkPolicyDiagram.tsx +9 -16
- package/src/components/resources/renderers/NetworkPolicyRenderer.test.tsx +75 -0
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +50 -36
- package/src/components/resources/renderers/PodRenderer.tsx +11 -0
- package/src/components/resources/renderers/PolicyCoverageSection.tsx +7 -4
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +13 -15
- package/src/components/resources/renderers/ReflectorSection.test.tsx +281 -0
- package/src/components/resources/renderers/ReflectorSection.tsx +296 -0
- package/src/components/resources/renderers/RolloutRenderer.test.tsx +117 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +219 -56
- package/src/components/resources/renderers/SbomReportRenderer.tsx +10 -4
- package/src/components/resources/renderers/SecretRenderer.tsx +10 -3
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +10 -4
- package/src/components/resources/renderers/WorkloadRenderer.tsx +11 -1
- package/src/components/resources/renderers/index.ts +3 -0
- package/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx +199 -0
- package/src/components/resources/renderers/rollout/ReplicaSetProgression.tsx +123 -0
- package/src/components/resources/resource-utils.ts +18 -9
- package/src/components/shared/CreateResourceDialog.tsx +237 -14
- package/src/components/shared/DrainPlanDialog.test.tsx +154 -0
- package/src/components/shared/DrainPlanDialog.tsx +297 -0
- package/src/components/shared/ResourceActionsBar.tsx +53 -26
- package/src/components/shared/ResourceRendererDispatch.test.tsx +45 -0
- package/src/components/shared/ResourceRendererDispatch.tsx +27 -6
- package/src/components/shared/index.ts +4 -0
- package/src/components/timeline/TimelineList.tsx +14 -11
- package/src/components/timeline/TimelineStrip.tsx +26 -4
- package/src/components/timeline/TimelineSwimlanes.tsx +3 -5
- package/src/components/timeline/TimelineToolbar.tsx +43 -10
- package/src/components/topology/TopologyControls.tsx +27 -8
- package/src/components/topology/TopologyGraph.tsx +14 -5
- package/src/components/trace/ReachabilityView.tsx +17 -16
- package/src/components/ui/CardSection.tsx +79 -35
- package/src/components/ui/Collapse.test.tsx +74 -0
- package/src/components/ui/Collapse.tsx +115 -29
- package/src/components/ui/DialogPortal.tsx +7 -2
- package/src/components/ui/FetchResult.test.tsx +20 -2
- package/src/components/ui/FetchResult.tsx +33 -3
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +7 -4
- package/src/components/ui/FreshnessControl.tsx +8 -2
- package/src/components/ui/RestrictedState.tsx +7 -4
- package/src/components/ui/RowActionMenu.tsx +20 -8
- package/src/components/ui/SelectMenu.tsx +18 -6
- package/src/components/ui/YamlEditor.tsx +7 -9
- package/src/components/ui/drawer-components.tsx +14 -21
- package/src/components/ui/index.ts +30 -22
- package/src/components/workload/WorkloadView.tsx +8 -12
- package/src/hooks/useAnimatedUnmount.ts +21 -3
- package/src/theme/components.css +20 -1
- package/src/types/core.ts +15 -1
- package/src/types/gitops-insights.ts +23 -0
- package/src/types/gitops-tree.ts +29 -0
- package/src/utils/animation.ts +93 -13
- package/src/utils/index.ts +1 -0
- package/src/utils/navigation.test.ts +20 -1
- package/src/utils/navigation.ts +25 -0
- package/src/utils/network-policy.test.ts +42 -0
- package/src/utils/network-policy.ts +59 -0
- package/src/utils/reflector.ts +139 -0
- package/src/utils/yaml-file-import.test.ts +189 -0
- package/src/utils/yaml-file-import.ts +95 -0
- package/src/components/logs/JsonLogLine.tsx +0 -93
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Check, X, AlertTriangle, Minus, Clock } from 'lucide-react'
|
|
2
|
+
import { clsx } from 'clsx'
|
|
3
|
+
import { canaryStepLabel, canaryStepTemplateRefs } from '../RolloutRenderer'
|
|
4
|
+
import { analysisPhaseLevel, healthColors, type HealthLevel } from '../../resource-utils'
|
|
5
|
+
|
|
6
|
+
export interface StepAnalysisStatus {
|
|
7
|
+
name?: string
|
|
8
|
+
status?: string
|
|
9
|
+
message?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface CanaryStepTimelineProps {
|
|
13
|
+
steps: any[]
|
|
14
|
+
currentStepIndex?: number
|
|
15
|
+
// The live "Step analysis" slot from rolloutAnalysisRuns(status) — shown
|
|
16
|
+
// inline on the current step when it's an analysis step, instead of only
|
|
17
|
+
// in the separate Analysis section above.
|
|
18
|
+
stepAnalysisStatus?: StepAnalysisStatus
|
|
19
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
20
|
+
namespace?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type StepState = 'completed' | 'current' | 'pending'
|
|
24
|
+
|
|
25
|
+
// Only 'unhealthy' (Failed/Error) and 'alert' (Inconclusive) colour the dot.
|
|
26
|
+
// A Successful or still-running analysis deliberately gets no special colour:
|
|
27
|
+
// the step itself may still be paused or in progress regardless, so it keeps
|
|
28
|
+
// the same "current" tone as any other in-progress step rather than reading
|
|
29
|
+
// as done.
|
|
30
|
+
function stepDotTone(state: StepState, analysisLevel?: HealthLevel) {
|
|
31
|
+
if (state === 'current' && (analysisLevel === 'unhealthy' || analysisLevel === 'alert')) {
|
|
32
|
+
return healthColors[analysisLevel]
|
|
33
|
+
}
|
|
34
|
+
switch (state) {
|
|
35
|
+
case 'completed':
|
|
36
|
+
return healthColors.healthy
|
|
37
|
+
case 'current':
|
|
38
|
+
return healthColors.neutral
|
|
39
|
+
default:
|
|
40
|
+
return 'bg-theme-hover text-theme-text-tertiary'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function StepDot({ state, analysisLevel }: { state: StepState; analysisLevel?: HealthLevel }) {
|
|
45
|
+
const tone = stepDotTone(state, analysisLevel)
|
|
46
|
+
let Icon = Minus
|
|
47
|
+
if (state === 'current' && analysisLevel === 'unhealthy') Icon = X
|
|
48
|
+
else if (state === 'current' && analysisLevel === 'alert') Icon = AlertTriangle
|
|
49
|
+
else if (state === 'completed') Icon = Check
|
|
50
|
+
else if (state === 'current') Icon = Clock
|
|
51
|
+
return (
|
|
52
|
+
<span className={clsx('z-10 mt-1 flex h-3 w-3 shrink-0 items-center justify-center rounded-full ring-2 ring-theme-surface', tone)}>
|
|
53
|
+
<Icon className="h-2 w-2" strokeWidth={4} />
|
|
54
|
+
</span>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Vertical connected stepper for a Rollout's canary steps — a linear
|
|
59
|
+
// sequence (no branching), so a simple top-to-bottom timeline reads more
|
|
60
|
+
// naturally than a graph. Same connecting-line visual as ConditionsSection.
|
|
61
|
+
export function CanaryStepTimeline({ steps, currentStepIndex, stepAnalysisStatus, onNavigate, namespace }: CanaryStepTimelineProps) {
|
|
62
|
+
if (steps.length === 0) return null
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="relative">
|
|
66
|
+
<div className="absolute bottom-2 left-[9px] top-2 w-px bg-theme-border" />
|
|
67
|
+
<div className="space-y-0.5">
|
|
68
|
+
{steps.map((step, index) => {
|
|
69
|
+
const state: StepState =
|
|
70
|
+
currentStepIndex === undefined ? 'pending' : index < currentStepIndex ? 'completed' : index === currentStepIndex ? 'current' : 'pending'
|
|
71
|
+
const isCurrent = state === 'current'
|
|
72
|
+
const label = canaryStepLabel(step)
|
|
73
|
+
const templateRefs = step.analysis ? canaryStepTemplateRefs(step) : []
|
|
74
|
+
const showAnalysisStatus = isCurrent && step.analysis && stepAnalysisStatus?.status
|
|
75
|
+
const analysisLevel: HealthLevel | undefined = showAnalysisStatus
|
|
76
|
+
? analysisPhaseLevel(stepAnalysisStatus!.status)
|
|
77
|
+
: undefined
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div key={index} className="relative flex items-start gap-2 py-1.5 pr-1 text-sm">
|
|
81
|
+
<StepDot state={state} analysisLevel={analysisLevel} />
|
|
82
|
+
<div className="min-w-0 flex-1 pl-1">
|
|
83
|
+
<div className="flex items-center gap-2">
|
|
84
|
+
<span className="text-[11px] text-theme-text-tertiary">#{index}</span>
|
|
85
|
+
<span className={clsx(isCurrent ? 'font-medium text-theme-text-primary' : state === 'completed' ? 'text-theme-text-secondary' : 'text-theme-text-tertiary')}>
|
|
86
|
+
{label}
|
|
87
|
+
</span>
|
|
88
|
+
{showAnalysisStatus && analysisLevel && (
|
|
89
|
+
onNavigate && stepAnalysisStatus!.name ? (
|
|
90
|
+
<button
|
|
91
|
+
onClick={() =>
|
|
92
|
+
onNavigate({ kind: 'AnalysisRun', namespace: namespace ?? '', name: stepAnalysisStatus!.name! })
|
|
93
|
+
}
|
|
94
|
+
className={clsx('badge-sm hover:underline', healthColors[analysisLevel])}
|
|
95
|
+
title={stepAnalysisStatus!.name}
|
|
96
|
+
>
|
|
97
|
+
{stepAnalysisStatus!.status}
|
|
98
|
+
</button>
|
|
99
|
+
) : (
|
|
100
|
+
<span className={clsx('badge-sm', healthColors[analysisLevel])}>
|
|
101
|
+
{stepAnalysisStatus!.status}
|
|
102
|
+
</span>
|
|
103
|
+
)
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
{templateRefs.length > 0 && (
|
|
107
|
+
<div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5">
|
|
108
|
+
{templateRefs.map((ref) => (
|
|
109
|
+
<button
|
|
110
|
+
key={ref.name}
|
|
111
|
+
onClick={() =>
|
|
112
|
+
onNavigate?.({
|
|
113
|
+
kind: ref.clusterScoped ? 'ClusterAnalysisTemplate' : 'AnalysisTemplate',
|
|
114
|
+
namespace: ref.clusterScoped ? '' : namespace ?? '',
|
|
115
|
+
name: ref.name,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
disabled={!onNavigate}
|
|
119
|
+
className={clsx(
|
|
120
|
+
'font-mono text-[11px]',
|
|
121
|
+
onNavigate ? 'text-brand hover:underline' : 'text-theme-text-tertiary'
|
|
122
|
+
)}
|
|
123
|
+
>
|
|
124
|
+
{ref.name}
|
|
125
|
+
</button>
|
|
126
|
+
))}
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
})}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface BlueGreenPhase {
|
|
139
|
+
label: string
|
|
140
|
+
state: 'completed' | 'current' | 'pending'
|
|
141
|
+
analysis?: { name?: string; status?: string }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Same connected-dot visual as CanaryStepTimeline, for blueGreenPhases()'s
|
|
145
|
+
// derived phase list — blueGreen has no steps[] array to iterate, so there's
|
|
146
|
+
// nothing to reuse structurally, but the "linear sequence, top to bottom"
|
|
147
|
+
// visual language should match.
|
|
148
|
+
export function BlueGreenTimeline({
|
|
149
|
+
phases,
|
|
150
|
+
onNavigate,
|
|
151
|
+
namespace,
|
|
152
|
+
}: {
|
|
153
|
+
phases: BlueGreenPhase[]
|
|
154
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
155
|
+
namespace?: string
|
|
156
|
+
}) {
|
|
157
|
+
if (phases.length === 0) return null
|
|
158
|
+
return (
|
|
159
|
+
<div className="relative">
|
|
160
|
+
<div className="absolute bottom-2 left-[9px] top-2 w-px bg-theme-border" />
|
|
161
|
+
<div className="space-y-0.5">
|
|
162
|
+
{phases.map((phase, index) => {
|
|
163
|
+
const level = phase.analysis?.status ? analysisPhaseLevel(phase.analysis.status) : undefined
|
|
164
|
+
return (
|
|
165
|
+
<div key={index} className="relative flex items-start gap-2 py-1.5 pr-1 text-sm">
|
|
166
|
+
<StepDot state={phase.state} analysisLevel={level} />
|
|
167
|
+
<span
|
|
168
|
+
className={clsx(
|
|
169
|
+
'pl-1',
|
|
170
|
+
phase.state === 'current'
|
|
171
|
+
? 'font-medium text-theme-text-primary'
|
|
172
|
+
: phase.state === 'completed'
|
|
173
|
+
? 'text-theme-text-secondary'
|
|
174
|
+
: 'text-theme-text-tertiary'
|
|
175
|
+
)}
|
|
176
|
+
>
|
|
177
|
+
{phase.label}
|
|
178
|
+
</span>
|
|
179
|
+
{level &&
|
|
180
|
+
(onNavigate && phase.analysis?.name ? (
|
|
181
|
+
<button
|
|
182
|
+
onClick={() =>
|
|
183
|
+
onNavigate({ kind: 'AnalysisRun', namespace: namespace ?? '', name: phase.analysis!.name! })
|
|
184
|
+
}
|
|
185
|
+
className={clsx('badge-sm hover:underline', healthColors[level])}
|
|
186
|
+
title={phase.analysis.name}
|
|
187
|
+
>
|
|
188
|
+
{phase.analysis.status}
|
|
189
|
+
</button>
|
|
190
|
+
) : (
|
|
191
|
+
<span className={clsx('badge-sm', healthColors[level])}>{phase.analysis!.status}</span>
|
|
192
|
+
))}
|
|
193
|
+
</div>
|
|
194
|
+
)
|
|
195
|
+
})}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useState, useId } from 'react'
|
|
2
|
+
import { Box } from 'lucide-react'
|
|
3
|
+
import { clsx } from 'clsx'
|
|
4
|
+
import { formatAge } from '../../resource-utils'
|
|
5
|
+
import { Collapse, CollapseChevron, disclosurePanelId } from '../../../ui/Collapse'
|
|
6
|
+
import { revisionRoleBadges } from '../../../shared/ResourceActionsBar'
|
|
7
|
+
import type { WorkloadRevision, WorkloadPodInfo } from '../../../../types'
|
|
8
|
+
|
|
9
|
+
interface ReplicaSetProgressionProps {
|
|
10
|
+
revisions: WorkloadRevision[]
|
|
11
|
+
pods?: WorkloadPodInfo[]
|
|
12
|
+
isRollout: true
|
|
13
|
+
namespace: string
|
|
14
|
+
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function imageTag(image: string): string {
|
|
18
|
+
const lastColon = image.lastIndexOf(':')
|
|
19
|
+
const lastSlash = image.lastIndexOf('/')
|
|
20
|
+
return lastColon > lastSlash ? image.slice(lastColon + 1) : image
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function podToneClass(pod: WorkloadPodInfo): string {
|
|
24
|
+
switch (pod.healthLevel) {
|
|
25
|
+
case 'healthy':
|
|
26
|
+
return 'status-healthy'
|
|
27
|
+
case 'degraded':
|
|
28
|
+
return 'status-degraded'
|
|
29
|
+
case 'unhealthy':
|
|
30
|
+
return 'status-unhealthy'
|
|
31
|
+
default:
|
|
32
|
+
return 'status-unknown'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Read-only revision tree: Rollout -> ReplicaSet revision -> pods, mirroring
|
|
37
|
+
// `kubectl argo rollouts get rollout`'s tree output. Newest first, matching
|
|
38
|
+
// the existing rollback-history dialog's ordering. Rollback stays exclusive
|
|
39
|
+
// to that dialog — this is purely informational.
|
|
40
|
+
export function ReplicaSetProgression({ revisions, pods, namespace, onNavigate }: ReplicaSetProgressionProps) {
|
|
41
|
+
const [expanded, setExpanded] = useState<Set<number>>(new Set())
|
|
42
|
+
// Revision rows are mapped inline, so they can't each call useDisclosure;
|
|
43
|
+
// one generated prefix plus the revision number keeps aria-controls unique.
|
|
44
|
+
const panelBase = useId()
|
|
45
|
+
|
|
46
|
+
const podsByHash = new Map<string, WorkloadPodInfo[]>()
|
|
47
|
+
for (const pod of pods ?? []) {
|
|
48
|
+
if (!pod.revisionIdentity) continue
|
|
49
|
+
const list = podsByHash.get(pod.revisionIdentity) ?? []
|
|
50
|
+
list.push(pod)
|
|
51
|
+
podsByHash.set(pod.revisionIdentity, list)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const toggle = (number: number) =>
|
|
55
|
+
setExpanded((prev) => {
|
|
56
|
+
const next = new Set(prev)
|
|
57
|
+
if (next.has(number)) next.delete(number)
|
|
58
|
+
else next.add(number)
|
|
59
|
+
return next
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className="space-y-1">
|
|
64
|
+
{revisions.map((rev) => {
|
|
65
|
+
const revPods = rev.podHash ? podsByHash.get(rev.podHash) ?? [] : []
|
|
66
|
+
// One predicate for header, caret and panel: no pods, nothing to open.
|
|
67
|
+
const isOpen = expanded.has(rev.number) && revPods.length > 0
|
|
68
|
+
return (
|
|
69
|
+
<div key={rev.number} className="rounded border border-theme-border/50">
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
aria-expanded={isOpen}
|
|
73
|
+
aria-controls={disclosurePanelId(panelBase, rev.number)}
|
|
74
|
+
onClick={() => toggle(rev.number)}
|
|
75
|
+
className="flex w-full items-center gap-2 px-2 py-1.5 text-left hover:bg-theme-hover"
|
|
76
|
+
disabled={revPods.length === 0}
|
|
77
|
+
>
|
|
78
|
+
{revPods.length > 0 ? (
|
|
79
|
+
<CollapseChevron open={isOpen} className="h-3.5 w-3.5" />
|
|
80
|
+
) : (
|
|
81
|
+
<span className="w-3.5 shrink-0" />
|
|
82
|
+
)}
|
|
83
|
+
<span className="font-mono text-xs text-theme-text-primary">#{rev.number}</span>
|
|
84
|
+
<span className="min-w-0 flex-1 truncate font-mono text-xs text-theme-text-secondary">{imageTag(rev.image)}</span>
|
|
85
|
+
<div className="flex shrink-0 items-center gap-1">
|
|
86
|
+
{revisionRoleBadges(rev, true).map(({ label, tone, tip }) => (
|
|
87
|
+
<span key={label} title={tip} className={clsx('badge', tone)}>
|
|
88
|
+
{label}
|
|
89
|
+
</span>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
92
|
+
<span className="shrink-0 text-xs tabular-nums text-theme-text-tertiary">{rev.replicas ?? 0} replicas</span>
|
|
93
|
+
<span className="shrink-0 text-xs text-theme-text-tertiary">{formatAge(rev.createdAt)}</span>
|
|
94
|
+
</button>
|
|
95
|
+
<Collapse open={isOpen} mountLazily id={disclosurePanelId(panelBase, rev.number)}>
|
|
96
|
+
<div className="space-y-0.5 border-t border-theme-border/50 px-2 py-1.5 pl-8">
|
|
97
|
+
{revPods.map((pod) => (
|
|
98
|
+
<div key={pod.name} className="flex items-center gap-2 py-0.5 text-xs">
|
|
99
|
+
<Box className="h-3 w-3 shrink-0 text-theme-text-tertiary" />
|
|
100
|
+
{onNavigate ? (
|
|
101
|
+
<button
|
|
102
|
+
onClick={() => onNavigate({ kind: 'Pod', namespace, name: pod.name })}
|
|
103
|
+
className="min-w-0 flex-1 truncate text-left font-mono text-theme-text-secondary hover:text-brand hover:underline"
|
|
104
|
+
>
|
|
105
|
+
{pod.name}
|
|
106
|
+
</button>
|
|
107
|
+
) : (
|
|
108
|
+
<span className="min-w-0 flex-1 truncate font-mono text-theme-text-secondary">{pod.name}</span>
|
|
109
|
+
)}
|
|
110
|
+
<span className={clsx('badge-sm', podToneClass(pod))}>{pod.phase || (pod.ready ? 'Ready' : 'Not Ready')}</span>
|
|
111
|
+
{(pod.restartCount ?? 0) > 0 && (
|
|
112
|
+
<span className="shrink-0 text-theme-text-tertiary">{pod.restartCount} restarts</span>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
))}
|
|
116
|
+
</div>
|
|
117
|
+
</Collapse>
|
|
118
|
+
</div>
|
|
119
|
+
)
|
|
120
|
+
})}
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Utility functions for resource display in tables
|
|
2
2
|
|
|
3
|
+
import { effectivePolicyTypeNames } from '../../utils/network-policy'
|
|
3
4
|
import { formatCPUString, formatMemoryString, formatBytes } from '../../utils/format'
|
|
4
5
|
import { pluralize } from '../../utils/pluralize'
|
|
5
6
|
import type { WorkloadPodInfo } from '../../types/core'
|
|
@@ -1544,24 +1545,33 @@ export function getRolloutStatus(rollout: any): StatusBadge {
|
|
|
1544
1545
|
return rolloutActivityBadge(getWorkloadRolloutActivity(rollout, 'rollout'))
|
|
1545
1546
|
}
|
|
1546
1547
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1548
|
+
/** The AnalysisPhase vocabulary (AnalysisRun, Experiment, and a Rollout's
|
|
1549
|
+
* analysis slots all report it) mapped onto HealthLevel. Shared so a caller
|
|
1550
|
+
* holding only the phase string lands on the same tone as one holding the
|
|
1551
|
+
* whole run. */
|
|
1552
|
+
export function analysisPhaseLevel(phase?: string): HealthLevel {
|
|
1549
1553
|
switch (phase) {
|
|
1550
1554
|
case 'Successful':
|
|
1551
|
-
return
|
|
1555
|
+
return 'healthy'
|
|
1552
1556
|
case 'Running':
|
|
1553
1557
|
case 'Pending':
|
|
1554
|
-
return
|
|
1558
|
+
return 'degraded'
|
|
1555
1559
|
case 'Inconclusive':
|
|
1556
|
-
return
|
|
1560
|
+
return 'alert'
|
|
1557
1561
|
case 'Failed':
|
|
1558
1562
|
case 'Error':
|
|
1559
|
-
return
|
|
1563
|
+
return 'unhealthy'
|
|
1560
1564
|
default:
|
|
1561
|
-
return
|
|
1565
|
+
return 'unknown'
|
|
1562
1566
|
}
|
|
1563
1567
|
}
|
|
1564
1568
|
|
|
1569
|
+
export function getAnalysisRunStatus(run: any): StatusBadge {
|
|
1570
|
+
const phase = run.status?.phase || 'Unknown'
|
|
1571
|
+
const level = analysisPhaseLevel(phase)
|
|
1572
|
+
return { text: phase, color: healthColors[level], level }
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1565
1575
|
// Counts only verdict-bearing metrics: Argo excludes dryRun results from the run's
|
|
1566
1576
|
// phase, and Running/Pending have not passed yet.
|
|
1567
1577
|
export function summarizeAnalysisMetrics(run: any): {
|
|
@@ -2068,8 +2078,7 @@ export function getWorkflowTemplateEntrypoint(wt: any): string {
|
|
|
2068
2078
|
// ============================================================================
|
|
2069
2079
|
|
|
2070
2080
|
export function getNetworkPolicyTypes(np: any): string {
|
|
2071
|
-
|
|
2072
|
-
return types.join(', ') || '-'
|
|
2081
|
+
return effectivePolicyTypeNames(np.spec).join(', ')
|
|
2073
2082
|
}
|
|
2074
2083
|
|
|
2075
2084
|
export function getNetworkPolicyRuleCount(np: any): { ingress: number; egress: number } {
|