@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
|
@@ -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
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useMemo } from 'react'
|
|
2
|
-
import { Package,
|
|
2
|
+
import { Package, Search } from 'lucide-react'
|
|
3
3
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
4
|
+
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
4
5
|
import { Input } from '../../ui/Input'
|
|
5
6
|
import { formatAge } from '../resource-utils'
|
|
6
7
|
import { formatTrivyImage } from './trivy-shared'
|
|
@@ -14,6 +15,7 @@ const INITIAL_SHOW_COUNT = 100
|
|
|
14
15
|
export function SbomReportRenderer({ data }: SbomReportRendererProps) {
|
|
15
16
|
const [showAll, setShowAll] = useState(false)
|
|
16
17
|
const [expanded, setExpanded] = useState(true)
|
|
18
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
17
19
|
const [searchTerm, setSearchTerm] = useState('')
|
|
18
20
|
|
|
19
21
|
const report = data.report || {}
|
|
@@ -63,13 +65,17 @@ export function SbomReportRenderer({ data }: SbomReportRendererProps) {
|
|
|
63
65
|
{bom.length > 0 && (
|
|
64
66
|
<Section title="Components">
|
|
65
67
|
<button
|
|
68
|
+
{...buttonProps}
|
|
66
69
|
onClick={() => setExpanded(!expanded)}
|
|
67
70
|
className="flex items-center gap-1 text-xs text-theme-text-secondary hover:text-theme-text-primary mb-2"
|
|
68
71
|
>
|
|
69
|
-
|
|
72
|
+
<CollapseChevron open={expanded} className="w-3.5 h-3.5" />
|
|
70
73
|
{bom.length} components
|
|
71
74
|
</button>
|
|
72
|
-
{
|
|
75
|
+
{/* The table can run to hundreds of rows; unmount it while closed
|
|
76
|
+
so a collapsed report stays cheap. Filters live above, so
|
|
77
|
+
nothing is lost. */}
|
|
78
|
+
<Collapse open={expanded} unmountOnExit id={panelId}>
|
|
73
79
|
<div className="overflow-x-auto -mx-1">
|
|
74
80
|
{/* Search */}
|
|
75
81
|
<div className="flex items-center gap-2 mb-2 px-1">
|
|
@@ -131,7 +137,7 @@ export function SbomReportRenderer({ data }: SbomReportRendererProps) {
|
|
|
131
137
|
</button>
|
|
132
138
|
)}
|
|
133
139
|
</div>
|
|
134
|
-
|
|
140
|
+
</Collapse>
|
|
135
141
|
</Section>
|
|
136
142
|
)}
|
|
137
143
|
</>
|
|
@@ -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}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useState, useMemo } from 'react'
|
|
2
|
-
import { Shield, Package, ChevronDown,
|
|
2
|
+
import { Shield, Package, ChevronDown, ChevronUp, ExternalLink, Search, ArrowUpDown, Wrench } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import { Section, PropertyList, Property } from '../../ui/drawer-components'
|
|
5
|
+
import { Collapse, CollapseChevron, useDisclosure } from '../../ui/Collapse'
|
|
5
6
|
import { Input } from '../../ui/Input'
|
|
6
7
|
import { formatAge } from '../resource-utils'
|
|
7
8
|
import { SEVERITY_BADGE_COLORS, SEVERITY_BAR_COLORS, SEVERITY_TEXT_COLORS, SEVERITY_ORDER, TrivyAlertBanner, formatTrivyImage } from './trivy-shared'
|
|
@@ -27,6 +28,7 @@ const COLUMNS: { key: SortColumn; label: string }[] = [
|
|
|
27
28
|
export function VulnerabilityReportRenderer({ data }: VulnerabilityReportRendererProps) {
|
|
28
29
|
const [showAll, setShowAll] = useState(false)
|
|
29
30
|
const [expanded, setExpanded] = useState(true)
|
|
31
|
+
const { panelId, buttonProps } = useDisclosure(expanded)
|
|
30
32
|
const [searchTerm, setSearchTerm] = useState('')
|
|
31
33
|
const [severityFilters, setSeverityFilters] = useState<Set<string>>(new Set())
|
|
32
34
|
const [fixableOnly, setFixableOnly] = useState(false)
|
|
@@ -211,13 +213,17 @@ export function VulnerabilityReportRenderer({ data }: VulnerabilityReportRendere
|
|
|
211
213
|
{vulnerabilities.length > 0 && (
|
|
212
214
|
<Section title="Vulnerabilities" icon={Package}>
|
|
213
215
|
<button
|
|
216
|
+
{...buttonProps}
|
|
214
217
|
onClick={() => setExpanded(!expanded)}
|
|
215
218
|
className="flex items-center gap-1 text-xs text-theme-text-secondary hover:text-theme-text-primary mb-2"
|
|
216
219
|
>
|
|
217
|
-
|
|
220
|
+
<CollapseChevron open={expanded} className="w-3.5 h-3.5" />
|
|
218
221
|
{vulnerabilities.length} vulnerabilities
|
|
219
222
|
</button>
|
|
220
|
-
{
|
|
223
|
+
{/* The table can run to hundreds of rows; unmount it while closed
|
|
224
|
+
so a collapsed report stays cheap. Filters live above, so
|
|
225
|
+
nothing is lost. */}
|
|
226
|
+
<Collapse open={expanded} unmountOnExit id={panelId}>
|
|
221
227
|
<div className="overflow-x-auto -mx-1">
|
|
222
228
|
{/* Search and filter toolbar */}
|
|
223
229
|
<div className="flex items-center gap-2 mb-2 px-1">
|
|
@@ -339,7 +345,7 @@ export function VulnerabilityReportRenderer({ data }: VulnerabilityReportRendere
|
|
|
339
345
|
</button>
|
|
340
346
|
)}
|
|
341
347
|
</div>
|
|
342
|
-
|
|
348
|
+
</Collapse>
|
|
343
349
|
</Section>
|
|
344
350
|
)}
|
|
345
351
|
</>
|
|
@@ -10,6 +10,7 @@ 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 { NamespaceLimitRangeLink } from './LimitRangeRenderer'
|
|
13
14
|
import { hpaStateLabel } from '../resource-utils-hpa'
|
|
14
15
|
import { hpaBadgeSeverity } from '../HPADiagnosisSummary'
|
|
15
16
|
import {
|
|
@@ -55,6 +56,9 @@ interface WorkloadRendererProps {
|
|
|
55
56
|
policyData?: PolicyResourceResponse | null
|
|
56
57
|
policyLoading?: boolean
|
|
57
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
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
// Extract real problems from workload status (excludes normal rollout progress)
|
|
@@ -126,7 +130,7 @@ function compactHPASummary(diagnosis: HPADiagnosis): string {
|
|
|
126
130
|
return diagnosis.summary
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
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) {
|
|
130
134
|
const status = data.status || {}
|
|
131
135
|
const spec = data.spec || {}
|
|
132
136
|
const metadata = data.metadata || {}
|
|
@@ -375,6 +379,12 @@ export function WorkloadRenderer({ kind, data, onNavigate, onViewPods, onScale,
|
|
|
375
379
|
|
|
376
380
|
<Section title="Pod Template" defaultExpanded={false}>
|
|
377
381
|
<PodTemplateSection template={spec.template} />
|
|
382
|
+
<NamespaceLimitRangeLink
|
|
383
|
+
namespace={metadata.namespace || ''}
|
|
384
|
+
names={namespaceLimitRangeNames}
|
|
385
|
+
scope="workload"
|
|
386
|
+
onNavigate={onNavigate}
|
|
387
|
+
/>
|
|
378
388
|
</Section>
|
|
379
389
|
|
|
380
390
|
<ConditionsSection conditions={status.conditions} />
|
|
@@ -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'
|