@skyhook-io/k8s-ui 1.14.4 → 1.14.6
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/audit/AuditCard.tsx +18 -7
- package/src/components/audit/AuditCoveragePresentation.test.tsx +45 -0
- package/src/components/charts/AreaChart.test.tsx +173 -0
- package/src/components/charts/AreaChart.tsx +208 -53
- package/src/components/charts/PrometheusChartsView.test.tsx +36 -0
- package/src/components/charts/PrometheusChartsView.tsx +205 -76
- package/src/components/charts/SeriesLegend.tsx +8 -3
- package/src/components/charts/annotations.ts +59 -0
- package/src/components/charts/axis.test.ts +48 -0
- package/src/components/charts/axis.ts +62 -0
- package/src/components/charts/colors.test.ts +27 -0
- package/src/components/charts/colors.ts +23 -0
- package/src/components/charts/format.test.ts +19 -0
- package/src/components/charts/format.ts +21 -1
- package/src/components/charts/index.ts +3 -1
- package/src/components/charts/nearestSample.test.ts +31 -0
- package/src/components/charts/nearestSample.ts +15 -0
- package/src/components/charts/types.ts +12 -0
- package/src/components/checks/ChecksView.tsx +20 -2
- package/src/components/gitops/GitOpsDetailLayout.tsx +13 -0
- 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/resources/HPADiagnosisSummary.test.tsx +178 -0
- package/src/components/resources/HPADiagnosisSummary.tsx +218 -0
- package/src/components/resources/ResourcesView.tsx +67 -2
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/index.ts +7 -0
- package/src/components/resources/renderers/AnalysisTemplateRenderer.tsx +150 -0
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +8 -1
- package/src/components/resources/renderers/ExperimentRenderer.tsx +179 -0
- package/src/components/resources/renderers/HPARenderer.tsx +4 -57
- 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/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/SecretRenderer.tsx +10 -3
- package/src/components/resources/renderers/WorkloadRenderer.tsx +15 -21
- 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 +116 -0
- package/src/components/resources/resource-utils.ts +18 -9
- package/src/components/shared/CreateResourceDialog.tsx +231 -11
- 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/topology/K8sResourceNode.test.ts +9 -0
- package/src/components/topology/K8sResourceNode.tsx +56 -5
- package/src/components/ui/Badge.tsx +5 -1
- package/src/components/ui/FetchResult.test.tsx +20 -2
- package/src/components/ui/FetchResult.tsx +33 -3
- package/src/components/ui/FreshnessControl.tsx +8 -2
- package/src/components/ui/index.ts +5 -0
- package/src/components/ui/monacoRuntime.ts +14 -0
- package/src/components/workload/WorkloadView.tsx +2 -2
- package/src/monaco-deep.d.ts +10 -0
- package/src/theme/components.css +16 -0
- package/src/types/core.ts +38 -11
- package/src/types/gitops-insights.ts +23 -0
- package/src/types/gitops-tree.ts +29 -0
- 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
|
@@ -4,8 +4,11 @@ import { clsx } from 'clsx'
|
|
|
4
4
|
import { AlertBanner, Section, PropertyList, Property, ConditionsSection, PodTemplateSection, type ConditionTone } from '../../ui/drawer-components'
|
|
5
5
|
import { Tooltip } from '../../ui/Tooltip'
|
|
6
6
|
import { ConfirmDialog } from '../../ui/ConfirmDialog'
|
|
7
|
-
import { formatAge, getRolloutStep } from '../resource-utils'
|
|
8
|
-
import {
|
|
7
|
+
import { formatAge, getRolloutStep, analysisPhaseLevel, healthColors } from '../resource-utils'
|
|
8
|
+
import { CanaryStepTimeline, BlueGreenTimeline } from './rollout/CanaryStepTimeline'
|
|
9
|
+
import { ReplicaSetProgression } from './rollout/ReplicaSetProgression'
|
|
10
|
+
import { LookupFailureNote } from './LookupFailureNote'
|
|
11
|
+
import type { WorkloadRevision, WorkloadPodInfo } from '../../../types'
|
|
9
12
|
|
|
10
13
|
export type RolloutAction = 'abort' | 'retry' | 'promote' | 'promote-full' | 'skip-step'
|
|
11
14
|
|
|
@@ -38,12 +41,39 @@ export interface RolloutActionSpec {
|
|
|
38
41
|
confirm?: RolloutActionConfirm
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
export interface AnalysisRunHistoryEntry {
|
|
45
|
+
name: string
|
|
46
|
+
phase: string
|
|
47
|
+
message?: string
|
|
48
|
+
trigger?: string
|
|
49
|
+
stepIndex?: number
|
|
50
|
+
createdAt: string
|
|
51
|
+
metricsTotal: number
|
|
52
|
+
metricsPassing: number
|
|
53
|
+
metricsNotPassing: number
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
interface RolloutRendererProps {
|
|
42
57
|
data: any
|
|
43
58
|
onNavigate?: (ref: { kind: string; namespace: string; name: string }) => void
|
|
44
59
|
capabilities?: RolloutCapabilities
|
|
45
60
|
onAction?: (action: RolloutAction) => void
|
|
46
61
|
pendingAction?: RolloutAction | null
|
|
62
|
+
// Host-fetched — full AnalysisRun history (not just the 4 "active" slots
|
|
63
|
+
// rolloutAnalysisRuns already covers). Omitted entirely, the History
|
|
64
|
+
// subsection just doesn't render; a library consumer that skips the fetch
|
|
65
|
+
// loses nothing else.
|
|
66
|
+
analysisRunHistory?: AnalysisRunHistoryEntry[]
|
|
67
|
+
// The history fetch's error, so a refusal can be told apart from "no runs".
|
|
68
|
+
// Without it a caller lacking `list analysisruns` sees the section vanish and
|
|
69
|
+
// never learns the history exists.
|
|
70
|
+
analysisRunHistoryError?: unknown
|
|
71
|
+
// Host-fetched — the Rollout's ReplicaSet revision history + its pods, via
|
|
72
|
+
// the generic workload hooks (useWorkloadRevisions/useWorkloadPods) shared
|
|
73
|
+
// with every other workload kind, joined client-side by the host. Read-only
|
|
74
|
+
// here; the rollback action stays exclusively in the existing history dialog.
|
|
75
|
+
revisions?: WorkloadRevision[]
|
|
76
|
+
pods?: WorkloadPodInfo[]
|
|
47
77
|
}
|
|
48
78
|
|
|
49
79
|
// Has the controller reconciled the template as it stands? A rollback changes the
|
|
@@ -281,6 +311,94 @@ export function canaryStepLabel(step: any): string {
|
|
|
281
311
|
return key ? `Unrecognized step: ${key}` : 'Unknown step'
|
|
282
312
|
}
|
|
283
313
|
|
|
314
|
+
/** AnalysisTemplate/ClusterAnalysisTemplate references on either a canary
|
|
315
|
+
* step's analysis.templates[] array, or a single Experiment spec.analyses[]
|
|
316
|
+
* entry — the Experiment entry carries templateName/clusterScope flat on
|
|
317
|
+
* itself rather than nested under its own templates array, so it's wrapped
|
|
318
|
+
* into a one-element list to share the same mapping below. Both shapes use
|
|
319
|
+
* the SAME field name for the ref (templateName) regardless of scope —
|
|
320
|
+
* clusterScope is a separate boolean on the entry, not a distinct field
|
|
321
|
+
* name, so it's read directly rather than inferred from which field is set. */
|
|
322
|
+
export function canaryStepTemplateRefs(step: any): Array<{ name: string; clusterScoped: boolean }> {
|
|
323
|
+
const templates = step?.analysis?.templates || step?.templates || (step?.templateName ? [step] : [])
|
|
324
|
+
return templates
|
|
325
|
+
.map((t: any) => (t.templateName ? { name: t.templateName, clusterScoped: !!t.clusterScope } : null))
|
|
326
|
+
.filter(Boolean)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Ordered phase list for a blueGreen Rollout — there's no steps[] array to
|
|
330
|
+
* iterate, so this derives an equivalent sequence from strategy config
|
|
331
|
+
* crossed with live status. */
|
|
332
|
+
export function blueGreenPhases(data: any): Array<{
|
|
333
|
+
label: string
|
|
334
|
+
state: 'completed' | 'current' | 'pending'
|
|
335
|
+
analysis?: { name?: string; status?: string }
|
|
336
|
+
}> {
|
|
337
|
+
const spec = data?.spec?.strategy?.blueGreen || {}
|
|
338
|
+
const status = data?.status || {}
|
|
339
|
+
const bg = status.blueGreen || {}
|
|
340
|
+
// previewSelector is transient — Argo Rollouts clears it once there's no
|
|
341
|
+
// active preview to track (a settled, fully-promoted blueGreen commonly
|
|
342
|
+
// has none), so activeSelector === previewSelector alone under-detects
|
|
343
|
+
// completion for anything but the narrow just-promoted window. currentPodHash
|
|
344
|
+
// is a generic, always-populated status field (used by every strategy, not
|
|
345
|
+
// just blueGreen) naming the newest ReplicaSet's hash — activeSelector
|
|
346
|
+
// matching IT is the more durable "the latest revision is live" signal,
|
|
347
|
+
// true both right after cutover and indefinitely after.
|
|
348
|
+
const promoted = !!bg.activeSelector && (bg.activeSelector === status.currentPodHash || bg.activeSelector === bg.previewSelector)
|
|
349
|
+
|
|
350
|
+
// scaleUpPreviewCheckPoint is only set in a narrow preview-replica-count
|
|
351
|
+
// configuration and is absent on most real blueGreen Rollouts (confirmed
|
|
352
|
+
// live) — pre-promotion analysis existing at all, or the Rollout having
|
|
353
|
+
// reached ANY blueGreen pause, are both proof the preview already scaled
|
|
354
|
+
// up (neither can happen before it does), so either is equally valid
|
|
355
|
+
// "done" evidence even when the checkpoint field itself is never set.
|
|
356
|
+
const previewScaledUp =
|
|
357
|
+
!!bg.scaleUpPreviewCheckPoint || !!bg.prePromotionAnalysisRunStatus || status.phase === 'Paused' || promoted
|
|
358
|
+
|
|
359
|
+
const steps: Array<{ label: string; done: boolean; analysis?: { name?: string; status?: string } }> = [
|
|
360
|
+
{ label: 'Preview scaled up', done: previewScaledUp },
|
|
361
|
+
]
|
|
362
|
+
|
|
363
|
+
if (spec.prePromotionAnalysis) {
|
|
364
|
+
steps.push({
|
|
365
|
+
label: 'Pre-promotion analysis',
|
|
366
|
+
done: bg.prePromotionAnalysisRunStatus?.status === 'Successful' || promoted,
|
|
367
|
+
analysis: bg.prePromotionAnalysisRunStatus,
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
steps.push({
|
|
372
|
+
label:
|
|
373
|
+
spec.autoPromotionEnabled === false
|
|
374
|
+
? 'Awaiting manual promotion'
|
|
375
|
+
: spec.autoPromotionSeconds !== undefined
|
|
376
|
+
? `Auto-promote after ${spec.autoPromotionSeconds}s`
|
|
377
|
+
: 'Awaiting promotion',
|
|
378
|
+
done: promoted,
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
steps.push({ label: 'Active cutover', done: promoted })
|
|
382
|
+
|
|
383
|
+
if (spec.postPromotionAnalysis) {
|
|
384
|
+
steps.push({
|
|
385
|
+
label: 'Post-promotion analysis',
|
|
386
|
+
done: bg.postPromotionAnalysisRunStatus?.status === 'Successful',
|
|
387
|
+
analysis: bg.postPromotionAnalysisRunStatus,
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let currentAssigned = false
|
|
392
|
+
return steps.map((s) => {
|
|
393
|
+
if (s.done) return { label: s.label, state: 'completed' as const, analysis: s.analysis }
|
|
394
|
+
if (!currentAssigned) {
|
|
395
|
+
currentAssigned = true
|
|
396
|
+
return { label: s.label, state: 'current' as const, analysis: s.analysis }
|
|
397
|
+
}
|
|
398
|
+
return { label: s.label, state: 'pending' as const, analysis: s.analysis }
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
|
|
284
402
|
/** Populated analysis slots, in the order the controller runs them. */
|
|
285
403
|
export function rolloutAnalysisRuns(
|
|
286
404
|
status: any
|
|
@@ -294,23 +412,10 @@ export function rolloutAnalysisRuns(
|
|
|
294
412
|
}
|
|
295
413
|
|
|
296
414
|
function analysisStatusClass(status?: string): string {
|
|
297
|
-
|
|
298
|
-
case 'Successful':
|
|
299
|
-
return 'status-healthy'
|
|
300
|
-
case 'Running':
|
|
301
|
-
case 'Pending':
|
|
302
|
-
return 'status-degraded'
|
|
303
|
-
case 'Inconclusive':
|
|
304
|
-
return 'status-alert'
|
|
305
|
-
case 'Failed':
|
|
306
|
-
case 'Error':
|
|
307
|
-
return 'status-unhealthy'
|
|
308
|
-
default:
|
|
309
|
-
return 'status-unknown'
|
|
310
|
-
}
|
|
415
|
+
return healthColors[analysisPhaseLevel(status)]
|
|
311
416
|
}
|
|
312
417
|
|
|
313
|
-
export function RolloutRenderer({ data, onNavigate, capabilities, onAction, pendingAction }: RolloutRendererProps) {
|
|
418
|
+
export function RolloutRenderer({ data, onNavigate, capabilities, onAction, pendingAction, analysisRunHistory, analysisRunHistoryError, revisions, pods }: RolloutRendererProps) {
|
|
314
419
|
const [confirming, setConfirming] = useState<RolloutActionSpec | null>(null)
|
|
315
420
|
const status = data.status || {}
|
|
316
421
|
const spec = data.spec || {}
|
|
@@ -339,6 +444,7 @@ export function RolloutRenderer({ data, onNavigate, capabilities, onAction, pend
|
|
|
339
444
|
const analysisRuns = rolloutAnalysisRuns(status)
|
|
340
445
|
const problems = rolloutProblems(data)
|
|
341
446
|
const actions = onAction ? rolloutActions(data, capabilities) : []
|
|
447
|
+
const blueGreenPhaseList = blueGreenStrategy ? blueGreenPhases(data) : []
|
|
342
448
|
|
|
343
449
|
// Phase badge color
|
|
344
450
|
const phaseColor = (() => {
|
|
@@ -443,7 +549,27 @@ export function RolloutRenderer({ data, onNavigate, capabilities, onAction, pend
|
|
|
443
549
|
<Property label="Canary Service" value={canaryStrategy.canaryService} />
|
|
444
550
|
<Property label="Stable Service" value={canaryStrategy.stableService} />
|
|
445
551
|
{status.canary?.currentExperiment && (
|
|
446
|
-
<Property
|
|
552
|
+
<Property
|
|
553
|
+
label="Running Experiment"
|
|
554
|
+
value={
|
|
555
|
+
onNavigate ? (
|
|
556
|
+
<button
|
|
557
|
+
onClick={() =>
|
|
558
|
+
onNavigate({
|
|
559
|
+
kind: 'Experiment',
|
|
560
|
+
namespace: data?.metadata?.namespace ?? '',
|
|
561
|
+
name: status.canary.currentExperiment,
|
|
562
|
+
})
|
|
563
|
+
}
|
|
564
|
+
className="font-mono text-xs text-brand hover:underline"
|
|
565
|
+
>
|
|
566
|
+
{status.canary.currentExperiment}
|
|
567
|
+
</button>
|
|
568
|
+
) : (
|
|
569
|
+
status.canary.currentExperiment
|
|
570
|
+
)
|
|
571
|
+
}
|
|
572
|
+
/>
|
|
447
573
|
)}
|
|
448
574
|
</>
|
|
449
575
|
) : blueGreenStrategy ? (
|
|
@@ -584,54 +710,91 @@ export function RolloutRenderer({ data, onNavigate, capabilities, onAction, pend
|
|
|
584
710
|
</Section>
|
|
585
711
|
)}
|
|
586
712
|
|
|
587
|
-
{/* Canary Steps visual */}
|
|
588
713
|
{isCanary && steps.length > 0 && (
|
|
589
714
|
<Section title={`Canary Steps (${steps.length})`} defaultExpanded>
|
|
590
|
-
<
|
|
591
|
-
{steps
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
715
|
+
<CanaryStepTimeline
|
|
716
|
+
steps={steps}
|
|
717
|
+
currentStepIndex={currentStepIndex}
|
|
718
|
+
stepAnalysisStatus={analysisRuns.find((r) => r.label === 'Step analysis')}
|
|
719
|
+
onNavigate={onNavigate}
|
|
720
|
+
namespace={data?.metadata?.namespace}
|
|
721
|
+
/>
|
|
722
|
+
</Section>
|
|
723
|
+
)}
|
|
597
724
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
>
|
|
608
|
-
{/* Status indicator */}
|
|
609
|
-
<span
|
|
610
|
-
className={clsx(
|
|
611
|
-
'w-5 h-5 rounded-full flex items-center justify-center text-xs shrink-0',
|
|
612
|
-
isCompleted && 'status-green',
|
|
613
|
-
isCurrent && 'status-blue',
|
|
614
|
-
isPending && BADGE_INACTIVE
|
|
615
|
-
)}
|
|
616
|
-
>
|
|
617
|
-
{isCompleted ? '\u2713' : isCurrent ? '\u25CF' : '\u25CB'}
|
|
618
|
-
</span>
|
|
725
|
+
{!isCanary && blueGreenStrategy && blueGreenPhaseList.length > 0 && (
|
|
726
|
+
<Section title="Progression" defaultExpanded>
|
|
727
|
+
<BlueGreenTimeline
|
|
728
|
+
phases={blueGreenPhaseList}
|
|
729
|
+
onNavigate={onNavigate}
|
|
730
|
+
namespace={data?.metadata?.namespace}
|
|
731
|
+
/>
|
|
732
|
+
</Section>
|
|
733
|
+
)}
|
|
619
734
|
|
|
620
|
-
|
|
621
|
-
|
|
735
|
+
{(revisions?.length ?? 0) > 0 && (
|
|
736
|
+
<Section title={`ReplicaSets (${revisions!.length})`}>
|
|
737
|
+
<ReplicaSetProgression
|
|
738
|
+
revisions={revisions!}
|
|
739
|
+
pods={pods}
|
|
740
|
+
isRollout
|
|
741
|
+
namespace={data?.metadata?.namespace ?? ''}
|
|
742
|
+
onNavigate={onNavigate}
|
|
743
|
+
/>
|
|
744
|
+
</Section>
|
|
745
|
+
)}
|
|
622
746
|
|
|
623
|
-
|
|
747
|
+
{/* Backward-looking, so it sits below the live progression and stays collapsed */}
|
|
748
|
+
{(analysisRunHistory?.length || analysisRunHistoryError) && (
|
|
749
|
+
<Section
|
|
750
|
+
// Section reads defaultExpanded once, on mount, so a refusal arriving
|
|
751
|
+
// later would leave its note sealed inside a collapsed section that
|
|
752
|
+
// still reads as a healthy list. Keying on the refusal remounts the
|
|
753
|
+
// section so it opens the moment the list stops being trustworthy.
|
|
754
|
+
key={analysisRunHistoryError ? 'unreadable' : 'readable'}
|
|
755
|
+
title={analysisRunHistory?.length ? `AnalysisRun History (${analysisRunHistory.length})` : 'AnalysisRun History'}
|
|
756
|
+
icon={Activity}
|
|
757
|
+
// History is backward-looking, so it stays out of the way until it
|
|
758
|
+
// cannot be read.
|
|
759
|
+
defaultExpanded={!!analysisRunHistoryError}
|
|
760
|
+
>
|
|
761
|
+
<LookupFailureNote
|
|
762
|
+
errors={[analysisRunHistoryError]}
|
|
763
|
+
what="this Rollout’s analysis history"
|
|
764
|
+
incomplete={!!analysisRunHistory?.length}
|
|
765
|
+
/>
|
|
766
|
+
<div className="space-y-1">
|
|
767
|
+
{(analysisRunHistory ?? []).map((run) => (
|
|
768
|
+
<div key={run.name} className="flex items-center gap-2 rounded px-2 py-1.5 text-sm">
|
|
769
|
+
<span className={clsx('badge', analysisStatusClass(run.phase))}>{run.phase || 'Unknown'}</span>
|
|
770
|
+
{onNavigate ? (
|
|
771
|
+
<button
|
|
772
|
+
onClick={() =>
|
|
773
|
+
onNavigate({ kind: 'AnalysisRun', namespace: data?.metadata?.namespace ?? '', name: run.name })
|
|
774
|
+
}
|
|
775
|
+
className="font-mono text-xs text-brand hover:underline"
|
|
776
|
+
>
|
|
777
|
+
{run.name}
|
|
778
|
+
</button>
|
|
779
|
+
) : (
|
|
780
|
+
<span className="font-mono text-xs text-theme-text-secondary">{run.name}</span>
|
|
781
|
+
)}
|
|
782
|
+
{run.trigger && <span className="text-xs text-theme-text-tertiary">{run.trigger}</span>}
|
|
783
|
+
{run.metricsTotal > 0 && (
|
|
624
784
|
<span
|
|
625
785
|
className={clsx(
|
|
626
|
-
'text-
|
|
627
|
-
|
|
786
|
+
'ml-auto text-xs',
|
|
787
|
+
run.metricsNotPassing > 0 ? 'text-amber-400' : 'text-theme-text-tertiary'
|
|
628
788
|
)}
|
|
629
789
|
>
|
|
630
|
-
{
|
|
790
|
+
{run.metricsPassing}/{run.metricsTotal} passing
|
|
631
791
|
</span>
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
792
|
+
)}
|
|
793
|
+
<span className={clsx('text-xs text-theme-text-tertiary', run.metricsTotal === 0 && 'ml-auto')}>
|
|
794
|
+
{formatAge(run.createdAt)}
|
|
795
|
+
</span>
|
|
796
|
+
</div>
|
|
797
|
+
))}
|
|
635
798
|
</div>
|
|
636
799
|
</Section>
|
|
637
800
|
)}
|
|
@@ -4,12 +4,15 @@ import { clsx } from 'clsx'
|
|
|
4
4
|
import { stringify as yamlStringify } from 'yaml'
|
|
5
5
|
import { Section, PropertyList, Property, AlertBanner } from '../../ui/drawer-components'
|
|
6
6
|
import { ConfirmDialog } from '../../ui/ConfirmDialog'
|
|
7
|
-
import
|
|
7
|
+
import { ReflectorSummary, ReflectorSection, isReflectorMirror, reflectorEditNotice } from './ReflectorSection'
|
|
8
|
+
import type { SecretCertificateInfo, CertificateInfo, Relationships, ResourceRef } from '../../../types'
|
|
8
9
|
import { pluralize } from '../../../utils/pluralize'
|
|
9
10
|
import { cleanResourceForYaml } from '../../../utils/yaml'
|
|
10
11
|
|
|
11
12
|
interface SecretRendererProps {
|
|
12
13
|
data: any
|
|
14
|
+
relationships?: Relationships
|
|
15
|
+
onNavigate?: (ref: ResourceRef) => void
|
|
13
16
|
certificateInfo?: SecretCertificateInfo
|
|
14
17
|
resourceData?: any
|
|
15
18
|
onSaveSecretValue?: (yaml: string) => Promise<void>
|
|
@@ -22,7 +25,7 @@ function formatDate(dateStr: string): string {
|
|
|
22
25
|
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' })
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
export function SecretRenderer({ data, certificateInfo, resourceData, onSaveSecretValue, isSaving }: SecretRendererProps) {
|
|
28
|
+
export function SecretRenderer({ data, relationships, onNavigate, certificateInfo, resourceData, onSaveSecretValue, isSaving }: SecretRendererProps) {
|
|
26
29
|
const [revealed, setRevealed] = useState<Set<string>>(new Set())
|
|
27
30
|
const [copied, setCopied] = useState<string | null>(null)
|
|
28
31
|
const [editingKey, setEditingKey] = useState<string | null>(null)
|
|
@@ -137,6 +140,8 @@ export function SecretRenderer({ data, certificateInfo, resourceData, onSaveSecr
|
|
|
137
140
|
/>
|
|
138
141
|
)}
|
|
139
142
|
|
|
143
|
+
<ReflectorSummary data={data} reflection={relationships?.reflection} onNavigate={onNavigate} />
|
|
144
|
+
|
|
140
145
|
{/* Certificate info section */}
|
|
141
146
|
{certs && certs.length > 0 && (
|
|
142
147
|
<>
|
|
@@ -260,6 +265,8 @@ export function SecretRenderer({ data, certificateInfo, resourceData, onSaveSecr
|
|
|
260
265
|
</div>
|
|
261
266
|
</Section>
|
|
262
267
|
|
|
268
|
+
<ReflectorSection data={data} reflection={relationships?.reflection} onNavigate={onNavigate} />
|
|
269
|
+
|
|
263
270
|
{editingKey && (
|
|
264
271
|
<ConfirmDialog
|
|
265
272
|
open={showSaveConfirm}
|
|
@@ -267,7 +274,7 @@ export function SecretRenderer({ data, certificateInfo, resourceData, onSaveSecr
|
|
|
267
274
|
onConfirm={() => handleSave(editingKey, editValue)}
|
|
268
275
|
title="Update Secret"
|
|
269
276
|
message={`Update key "${editingKey}" in secret "${data.metadata?.name || 'unknown'}"?`}
|
|
270
|
-
details="This will modify the secret value in the cluster immediately."
|
|
277
|
+
details={isReflectorMirror(data) ? `This will modify the secret value immediately. ${reflectorEditNotice}` : "This will modify the secret value in the cluster immediately."}
|
|
271
278
|
confirmLabel="Update"
|
|
272
279
|
variant="warning"
|
|
273
280
|
isLoading={isSaving}
|
|
@@ -6,11 +6,13 @@ import type { PolicyResourceResponse } from '../../../types/policy'
|
|
|
6
6
|
import { Section, PropertyList, Property, ConditionsSection, PodTemplateSection, AlertBanner, ResourceLink, ResourceRefBadge, useOperationalIssuesShown } from '../../ui/drawer-components'
|
|
7
7
|
import { DialogPortal } from '../../ui/DialogPortal'
|
|
8
8
|
import { Tooltip } from '../../ui/Tooltip'
|
|
9
|
-
import { Badge
|
|
9
|
+
import { Badge } from '../../ui/Badge'
|
|
10
10
|
import type { RBACSubjectResponse, RBACPolicyRule, ResourceRef, HPADiagnosis, WorkloadPodInfo } from '../../../types'
|
|
11
11
|
import { detectBlastRadius, rulePermissivenessScore } from '../../../utils/rbac-blast-radius'
|
|
12
12
|
import { RBACErrorSection, isRBACUnavailable } from './RBACErrorSection'
|
|
13
|
-
import {
|
|
13
|
+
import { NamespaceLimitRangeLink } from './LimitRangeRenderer'
|
|
14
|
+
import { hpaStateLabel } from '../resource-utils-hpa'
|
|
15
|
+
import { hpaBadgeSeverity } from '../HPADiagnosisSummary'
|
|
14
16
|
import {
|
|
15
17
|
rbacVerbBadgeClass,
|
|
16
18
|
rbacResourceBadgeClass,
|
|
@@ -54,6 +56,9 @@ interface WorkloadRendererProps {
|
|
|
54
56
|
policyData?: PolicyResourceResponse | null
|
|
55
57
|
policyLoading?: boolean
|
|
56
58
|
policyError?: Error | null
|
|
59
|
+
/** Names of the LimitRanges in this workload's namespace. Absent while the
|
|
60
|
+
* lookup is pending or unreadable, which leaves the link unrendered. */
|
|
61
|
+
namespaceLimitRangeNames?: string[] | null
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
// Extract real problems from workload status (excludes normal rollout progress)
|
|
@@ -125,7 +130,7 @@ function compactHPASummary(diagnosis: HPADiagnosis): string {
|
|
|
125
130
|
return diagnosis.summary
|
|
126
131
|
}
|
|
127
132
|
|
|
128
|
-
export function WorkloadRenderer({ kind, data, onNavigate, onViewPods, onScale, isScalePending, scaleBlockedBy, scalerDiagnostics, workloadPods, onRequestRefresh, rbacData, rbacLoading, rbacError, policyData, policyLoading, policyError }: WorkloadRendererProps) {
|
|
133
|
+
export function WorkloadRenderer({ kind, data, onNavigate, onViewPods, onScale, isScalePending, scaleBlockedBy, scalerDiagnostics, workloadPods, onRequestRefresh, rbacData, rbacLoading, rbacError, policyData, policyLoading, policyError, namespaceLimitRangeNames }: WorkloadRendererProps) {
|
|
129
134
|
const status = data.status || {}
|
|
130
135
|
const spec = data.spec || {}
|
|
131
136
|
const metadata = data.metadata || {}
|
|
@@ -374,6 +379,12 @@ export function WorkloadRenderer({ kind, data, onNavigate, onViewPods, onScale,
|
|
|
374
379
|
|
|
375
380
|
<Section title="Pod Template" defaultExpanded={false}>
|
|
376
381
|
<PodTemplateSection template={spec.template} />
|
|
382
|
+
<NamespaceLimitRangeLink
|
|
383
|
+
namespace={metadata.namespace || ''}
|
|
384
|
+
names={namespaceLimitRangeNames}
|
|
385
|
+
scope="workload"
|
|
386
|
+
onNavigate={onNavigate}
|
|
387
|
+
/>
|
|
377
388
|
</Section>
|
|
378
389
|
|
|
379
390
|
<ConditionsSection conditions={status.conditions} />
|
|
@@ -407,29 +418,12 @@ function ScalerDiagnosisRow({ entry }: { entry: ScalerDiagnosis }) {
|
|
|
407
418
|
if (!entry.diagnosis) return null
|
|
408
419
|
return (
|
|
409
420
|
<div className="rounded border border-theme-border bg-theme-surface px-2 py-1.5 text-xs leading-6 text-theme-text-secondary whitespace-normal break-normal">
|
|
410
|
-
<Badge severity={
|
|
421
|
+
<Badge severity={hpaBadgeSeverity(entry.diagnosis.state)} size="sm" className="mr-1.5 align-middle">{hpaStateLabel(entry.diagnosis.state)}</Badge>
|
|
411
422
|
<span className="align-middle whitespace-normal break-normal">{compactHPASummary(entry.diagnosis)}</span>
|
|
412
423
|
</div>
|
|
413
424
|
)
|
|
414
425
|
}
|
|
415
426
|
|
|
416
|
-
function badgeSeverityForHPA(diagnosis: HPADiagnosis): BadgeSeverity {
|
|
417
|
-
switch (hpaStateLevel(diagnosis.state)) {
|
|
418
|
-
case 'healthy':
|
|
419
|
-
return 'success'
|
|
420
|
-
case 'unhealthy':
|
|
421
|
-
return 'error'
|
|
422
|
-
case 'degraded':
|
|
423
|
-
return 'warning'
|
|
424
|
-
case 'alert':
|
|
425
|
-
return 'alert'
|
|
426
|
-
case 'neutral':
|
|
427
|
-
return 'info'
|
|
428
|
-
default:
|
|
429
|
-
return 'neutral'
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
427
|
// ============================================================================
|
|
434
428
|
// WORKLOAD PERMISSIONS SECTION
|
|
435
429
|
// ============================================================================
|
|
@@ -80,6 +80,7 @@ export * from './KyvernoPolicyShared'
|
|
|
80
80
|
export * from './KyvernoCELPolicyRenderers'
|
|
81
81
|
export * from './KyvernoExceptionRenderers'
|
|
82
82
|
export * from './LeaseRenderer'
|
|
83
|
+
export * from './LimitRangeRenderer'
|
|
83
84
|
export * from './LookupFailureNote'
|
|
84
85
|
export * from './NetworkPolicyRenderer'
|
|
85
86
|
export * from './NetworkPolicyDiagram'
|
|
@@ -102,6 +103,8 @@ export * from './RoleBindingRenderer'
|
|
|
102
103
|
export * from './RoleRenderer'
|
|
103
104
|
export * from './RolloutRenderer'
|
|
104
105
|
export * from './AnalysisRunRenderer'
|
|
106
|
+
export * from './AnalysisTemplateRenderer'
|
|
107
|
+
export * from './ExperimentRenderer'
|
|
105
108
|
export * from './RuntimeClassRenderer'
|
|
106
109
|
export * from './SbomReportRenderer'
|
|
107
110
|
export * from './SealedSecretRenderer'
|