@skyhook-io/k8s-ui 1.8.10 → 1.8.12
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 +7 -6
- package/src/assets/gitops/argocd.png +0 -0
- package/src/assets/gitops/flux.svg +7 -0
- package/src/components/applications/AppChips.tsx +10 -0
- package/src/components/applications/ApplicationDetail.test.tsx +697 -0
- package/src/components/applications/ApplicationDetail.tsx +3269 -474
- package/src/components/applications/ApplicationsView.tsx +62 -10
- package/src/components/applications/applications-view.test.tsx +47 -0
- package/src/components/applications/index.ts +1 -1
- package/src/components/audit/AuditAlerts.tsx +14 -10
- package/src/components/checks/ChecksView.tsx +2 -3
- package/src/components/checks/severity.ts +14 -10
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -2
- package/src/components/compare/CompareResourcePicker.tsx +2 -2
- package/src/components/gitops/GitOpsDetailLayout.tsx +13 -3
- package/src/components/gitops/GitOpsGraphFilterRail.tsx +2 -1
- package/src/components/gitops/GitOpsTableView.tsx +13 -6
- package/src/components/gitops/ManagedResourcesList.tsx +8 -11
- package/src/components/gitops/SyncCountdown.tsx +26 -17
- package/src/components/gitops/SyncOptionsDialog.test.ts +48 -0
- package/src/components/gitops/SyncOptionsDialog.tsx +190 -46
- package/src/components/gitops/insights/ArgoResourceDiff.tsx +172 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +474 -121
- package/src/components/gitops/insights/index.ts +1 -0
- package/src/components/gitops/insights/insights-helpers.test.ts +102 -1
- package/src/components/gitops/insights/insights-helpers.ts +74 -1
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +10 -2
- package/src/components/issues/IssuesView.tsx +91 -52
- package/src/components/issues/ResourceIssuesSection.test.tsx +39 -6
- package/src/components/issues/ResourceIssuesSection.tsx +46 -144
- package/src/components/issues/index.ts +3 -0
- package/src/components/issues/issue-timing.ts +53 -0
- package/src/components/issues/issues.test.ts +102 -1
- package/src/components/issues/severity.ts +3 -0
- package/src/components/logs/LogCore.tsx +14 -4
- package/src/components/logs/WorkloadLogsViewer.tsx +31 -6
- package/src/components/logs/useLogStream.ts +8 -1
- package/src/components/namespace-switcher/NamespacePicker.tsx +86 -107
- package/src/components/resources/ResourcesSidebar.test.tsx +71 -0
- package/src/components/resources/ResourcesSidebar.tsx +46 -46
- package/src/components/resources/ResourcesView.selected-kind.test.ts +143 -0
- package/src/components/resources/ResourcesView.tsx +233 -51
- package/src/components/resources/renderers/AWSMachineRenderer.tsx +3 -2
- package/src/components/resources/renderers/AWSManagedClusterRenderer.tsx +3 -2
- package/src/components/resources/renderers/AWSManagedControlPlaneRenderer.tsx +3 -2
- package/src/components/resources/renderers/AWSManagedMachinePoolRenderer.tsx +3 -2
- package/src/components/resources/renderers/AzureMachineRenderer.tsx +3 -2
- package/src/components/resources/renderers/AzureManagedControlPlaneRenderer.tsx +3 -2
- package/src/components/resources/renderers/AzureManagedMachinePoolRenderer.tsx +3 -2
- package/src/components/resources/renderers/CAPIClusterRenderer.tsx +3 -2
- package/src/components/resources/renderers/CAPIKubeadmControlPlaneRenderer.tsx +3 -2
- package/src/components/resources/renderers/CAPIMachineDeploymentRenderer.tsx +3 -2
- package/src/components/resources/renderers/CAPIMachineHealthCheckRenderer.tsx +3 -2
- package/src/components/resources/renderers/CAPIMachinePoolRenderer.tsx +3 -2
- package/src/components/resources/renderers/CAPIMachineRenderer.tsx +3 -2
- package/src/components/resources/renderers/CAPIMachineSetRenderer.tsx +3 -2
- package/src/components/resources/renderers/CertificateRenderer.tsx +3 -2
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +3 -2
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/CompositeRenderer.tsx +3 -2
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +99 -0
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +3 -2
- package/src/components/resources/renderers/GCPMachineRenderer.tsx +3 -2
- package/src/components/resources/renderers/GCPManagedControlPlaneRenderer.tsx +3 -2
- package/src/components/resources/renderers/GCPManagedMachinePoolRenderer.tsx +3 -2
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +7 -3
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +3 -2
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +7 -3
- package/src/components/resources/renderers/JobRenderer.tsx +3 -1
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +3 -2
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +3 -2
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +3 -2
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +3 -2
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +3 -2
- package/src/components/resources/renderers/ManagedResourceRenderer.tsx +3 -2
- package/src/components/resources/renderers/PVCRenderer.tsx +3 -2
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +2 -2
- package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +3 -2
- package/src/components/resources/renderers/SecretRenderer.tsx +2 -0
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +3 -2
- package/src/components/resources/renderers/ServiceRenderer.tsx +58 -40
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +7 -3
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkflowRenderer.test.tsx +43 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +60 -78
- package/src/components/resources/renderers/index.ts +1 -0
- package/src/components/resources/resource-utils-argo.test.ts +18 -1
- package/src/components/resources/resource-utils-argo.ts +8 -0
- package/src/components/resources/resource-utils-capi.ts +1 -1
- package/src/components/resources/resource-utils.ts +31 -0
- package/src/components/shared/DetailShell.tsx +2 -2
- package/src/components/shared/ResourceActionsBar.tsx +142 -111
- package/src/components/shared/ResourceRendererDispatch.tsx +33 -4
- package/src/components/shared/UnifiedDiff.tsx +51 -0
- package/src/components/shared/index.ts +1 -0
- package/src/components/timeline/TimelineList.tsx +375 -354
- package/src/components/timeline/TimelineStrip.test.tsx +194 -0
- package/src/components/timeline/TimelineStrip.tsx +836 -0
- package/src/components/timeline/TimelineSwimlanes.test.tsx +1028 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +2498 -593
- package/src/components/timeline/TimelineToolbar.test.tsx +320 -0
- package/src/components/timeline/TimelineToolbar.tsx +759 -0
- package/src/components/timeline/TimelineToolbarState.test.tsx +88 -0
- package/src/components/timeline/index.ts +23 -0
- package/src/components/timeline/scrubber-math.test.ts +235 -0
- package/src/components/timeline/scrubber-math.ts +249 -0
- package/src/components/timeline/shared.test.tsx +219 -0
- package/src/components/timeline/shared.tsx +232 -447
- package/src/components/timeline/timeline-filters.test.ts +196 -0
- package/src/components/timeline/timeline-filters.ts +180 -0
- package/src/components/timeline/timeline-lane-sort.test.ts +97 -0
- package/src/components/timeline/timeline-lane-sort.ts +77 -0
- package/src/components/timeline/timeline-live.test.ts +131 -0
- package/src/components/timeline/timeline-live.ts +77 -0
- package/src/components/topology/GroupNode.tsx +9 -2
- package/src/components/topology/K8sResourceNode.test.ts +11 -0
- package/src/components/topology/K8sResourceNode.tsx +387 -243
- package/src/components/topology/TopologyBreadcrumb.tsx +30 -0
- package/src/components/topology/TopologyControls.tsx +3 -1
- package/src/components/topology/TopologyFilterSidebar.tsx +9 -0
- package/src/components/topology/TopologyGraph.tsx +149 -90
- package/src/components/topology/TopologyOverlayBar.tsx +30 -0
- package/src/components/topology/TopologySearch.tsx +25 -17
- package/src/components/topology/index.ts +2 -0
- package/src/components/topology/layout.ts +10 -2
- package/src/components/topology/topology.css +0 -92
- package/src/components/ui/Badge.tsx +6 -1
- package/src/components/ui/CodeViewer.tsx +2 -2
- package/src/components/ui/Collapse.tsx +59 -0
- package/src/components/ui/ConfirmDialog.tsx +7 -3
- package/src/components/ui/DialogPortal.tsx +31 -9
- package/src/components/ui/Input.tsx +24 -0
- package/src/components/ui/MultiSelectPicker.test.tsx +101 -0
- package/src/components/ui/MultiSelectPicker.tsx +167 -0
- package/src/components/ui/PaneLoader.tsx +10 -2
- package/src/components/ui/ResourceBar.tsx +68 -25
- package/src/components/ui/SearchBox.tsx +18 -4
- package/src/components/ui/SearchPillInput.tsx +2 -2
- package/src/components/ui/SelectMenu.tsx +81 -0
- package/src/components/ui/drawer-components.tsx +48 -32
- package/src/components/ui/index.ts +6 -0
- package/src/components/ui/severity-tone.ts +11 -4
- package/src/components/workload/ResourceDetailDrawer.tsx +6 -2
- package/src/components/workload/WorkloadView.tsx +2706 -475
- package/src/components/workload/index.ts +1 -1
- package/src/theme/tailwind-theme.css +1 -0
- package/src/theme/variables.css +2 -1
- package/src/types/core.ts +57 -4
- package/src/types/gitops-insights.ts +69 -1
- package/src/types/gitops.ts +3 -3
- package/src/utils/application-history.test.ts +538 -0
- package/src/utils/application-history.ts +257 -0
- package/src/utils/application-topology.test.ts +198 -0
- package/src/utils/application-topology.ts +261 -0
- package/src/utils/applications.test.ts +201 -1
- package/src/utils/applications.ts +1182 -393
- package/src/utils/index.ts +3 -0
- package/src/utils/navigation.test.ts +32 -1
- package/src/utils/navigation.ts +70 -0
- package/src/utils/resource-hierarchy.test.ts +1024 -1
- package/src/utils/resource-hierarchy.ts +925 -267
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/topology-neighborhood.test.ts +202 -1
- package/src/utils/topology-neighborhood.ts +194 -13
- package/src/utils/workflow-execution.test.ts +556 -0
- package/src/utils/workflow-execution.ts +814 -0
- package/src/utils/workload-colors.ts +2 -3
|
@@ -17,6 +17,7 @@ import { useRegisterShortcuts } from '../../hooks/useKeyboardShortcuts'
|
|
|
17
17
|
import { pluralize } from '../../utils/pluralize'
|
|
18
18
|
import {
|
|
19
19
|
type AppEntry,
|
|
20
|
+
type AppRow,
|
|
20
21
|
type AppHealth,
|
|
21
22
|
type AppWorkloadClass,
|
|
22
23
|
type AppSource,
|
|
@@ -36,12 +37,34 @@ import {
|
|
|
36
37
|
isSystemNamespace,
|
|
37
38
|
searchTextForEntry,
|
|
38
39
|
foldAppGroups,
|
|
40
|
+
batchSignalForApp,
|
|
41
|
+
batchRuntimeForApp,
|
|
39
42
|
type FoldedRow,
|
|
40
43
|
} from '../../utils/applications'
|
|
41
44
|
import { ReadyBar } from './ReadyBar'
|
|
42
|
-
import { ProvenanceBadge, ClassBadge, CategoryChip, VersionInfo } from './AppChips'
|
|
45
|
+
import { ProvenanceBadge, ClassBadge, CategoryChip, VersionInfo, BatchSignalChip } from './AppChips'
|
|
43
46
|
import { AppIdentityTooltip, EnvHint } from './AppTooltips'
|
|
44
47
|
|
|
48
|
+
function ApplicationRuntimeCell({ apps, workloadClass, ready, desired }: { apps: AppRow[]; workloadClass: AppWorkloadClass; ready: number; desired: number }) {
|
|
49
|
+
if (workloadClass !== 'job') return <ReadyBar ready={ready} desired={desired} />
|
|
50
|
+
const runtimes = apps.map(batchRuntimeForApp)
|
|
51
|
+
const worst = runtimes.reduce((current, runtime) => (HEALTH_RANK[runtime.health] ?? 0) > (HEALTH_RANK[current.health] ?? 0) ? runtime : current, runtimes[0])
|
|
52
|
+
if (!worst) return <span className="text-xs text-theme-text-tertiary">Idle</span>
|
|
53
|
+
const same = runtimes.every((runtime) => runtime.label === worst.label)
|
|
54
|
+
const label = same ? worst.label : `${runtimes.length} states`
|
|
55
|
+
return (
|
|
56
|
+
<Tooltip content={runtimes.map((runtime, index) => `${apps[index]?.name ?? 'app'}: ${runtime.label} — ${runtime.detail}`).join('\n')} delay={150}>
|
|
57
|
+
<span className="inline-flex items-center gap-1.5 text-xs text-theme-text-secondary"><StatusDot tone={mapHealthToTone(worst.health)} />{label}</span>
|
|
58
|
+
</Tooltip>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function applicationRuntimeLabel(apps: AppRow[], workloadClass: AppWorkloadClass, health: AppHealth): string {
|
|
63
|
+
if (workloadClass !== 'job') return HEALTH_META[health].label
|
|
64
|
+
const labels = Array.from(new Set(apps.map((app) => batchRuntimeForApp(app).label)))
|
|
65
|
+
return labels.length === 1 ? labels[0] : 'Multiple batch states'
|
|
66
|
+
}
|
|
67
|
+
|
|
45
68
|
// ApplicationsView — the shared, variant-agnostic core behind the Applications
|
|
46
69
|
// surface. It owns the entire list chassis: a health hero header (PageHeader +
|
|
47
70
|
// SummaryTiles + DistributionBar), a left facet rail (Availability / Class /
|
|
@@ -52,7 +75,7 @@ import { AppIdentityTooltip, EnvHint } from './AppTooltips'
|
|
|
52
75
|
// the per-instance row. Styling mirrors the Resources table so the surfaces
|
|
53
76
|
// read as one design.
|
|
54
77
|
|
|
55
|
-
//
|
|
78
|
+
// Status is the one health facet — map app health onto the shared facet
|
|
56
79
|
// tone so its dots read red/amber/green/grey like the GitOps sync+health facets.
|
|
57
80
|
const HEALTH_TONE: Record<AppHealth, FacetTone> = {
|
|
58
81
|
unhealthy: 'error',
|
|
@@ -72,7 +95,7 @@ function compareEntries(a: AppEntry, b: AppEntry, key: SortKey): number {
|
|
|
72
95
|
case 'name':
|
|
73
96
|
return a.row.name.localeCompare(b.row.name)
|
|
74
97
|
case 'ready':
|
|
75
|
-
return a
|
|
98
|
+
return runtimeSortValue(a) - runtimeSortValue(b)
|
|
76
99
|
case 'version': {
|
|
77
100
|
// Sort by distinct-version count first (skewed apps cluster), then the
|
|
78
101
|
// first tag for a stable, human-meaningful order.
|
|
@@ -83,6 +106,10 @@ function compareEntries(a: AppEntry, b: AppEntry, key: SortKey): number {
|
|
|
83
106
|
}
|
|
84
107
|
}
|
|
85
108
|
|
|
109
|
+
function runtimeSortValue(entry: AppEntry): number {
|
|
110
|
+
return entry.workloadClass === 'job' ? (HEALTH_RANK[entry.health] ?? 0) : entry.readyRatio
|
|
111
|
+
}
|
|
112
|
+
|
|
86
113
|
// The env token an entry filters under. Single entries carry one env; fleet
|
|
87
114
|
// entries carry several — a fleet row matches an env facet if ANY of its slices
|
|
88
115
|
// do (the same inclusive policy as the Class facet).
|
|
@@ -348,7 +375,7 @@ export function ApplicationsView({ entries: allEntries, variant, onSelect, title
|
|
|
348
375
|
{initialLoading ? (
|
|
349
376
|
<BoardRailSkeleton
|
|
350
377
|
sections={[
|
|
351
|
-
['
|
|
378
|
+
['Status', 5],
|
|
352
379
|
['Class', 3],
|
|
353
380
|
['Type', 4],
|
|
354
381
|
['Environment', 3],
|
|
@@ -357,7 +384,7 @@ export function ApplicationsView({ entries: allEntries, variant, onSelect, title
|
|
|
357
384
|
/>
|
|
358
385
|
) : (
|
|
359
386
|
<>
|
|
360
|
-
<Facet icon={HeartPulse} title="
|
|
387
|
+
<Facet icon={HeartPulse} title="Status" options={HEALTH_ORDER.map((h) => ({ value: h, label: HEALTH_META[h].label, count: counts.health[h] ?? 0, tone: HEALTH_TONE[h] }))} selected={fHealth} onToggle={(v) => filters.toggle('health', v)} />
|
|
361
388
|
<Facet icon={Layers} title="Class" options={CLASS_ORDER.map((c) => ({ value: c, label: CLASS_META[c].label, count: counts.workloadClass[c] ?? 0 }))} selected={fClass} onToggle={(v) => filters.toggle('class', v)} />
|
|
362
389
|
<Facet icon={Shapes} title="Type" options={CATEGORY_ORDER.map((c) => ({ value: c, label: CATEGORY_META[c].label, count: counts.category[c] ?? 0, tooltip: CATEGORY_META[c].tooltip }))} selected={fType} onToggle={(v) => filters.toggle('type', v)} />
|
|
363
390
|
<Facet icon={Globe} title="Environment" info={<EnvHint />} options={envOptions} selected={fEnv} onToggle={(v) => filters.toggle('env', v)} />
|
|
@@ -430,7 +457,7 @@ export function ApplicationsView({ entries: allEntries, variant, onSelect, title
|
|
|
430
457
|
</>
|
|
431
458
|
)}
|
|
432
459
|
<th className={TH_CLASS}>Class</th>
|
|
433
|
-
<SortableTh label="
|
|
460
|
+
<SortableTh label="Runtime" sortKey="ready" activeKey={sort?.key ?? null} direction={sort?.dir ?? 'asc'} onSort={onSort} />
|
|
434
461
|
<SortableTh label="Version" sortKey="version" activeKey={sort?.key ?? null} direction={sort?.dir ?? 'asc'} onSort={onSort} />
|
|
435
462
|
<th className={TH_CLASS}>Workloads</th>
|
|
436
463
|
<th className={clsx(TH_CLASS, 'w-8')} />
|
|
@@ -452,11 +479,12 @@ export function ApplicationsView({ entries: allEntries, variant, onSelect, title
|
|
|
452
479
|
<span className="flex items-center gap-2">
|
|
453
480
|
{/* Left status stripe (worst-child health) — the row status
|
|
454
481
|
gutter shared with the GitOps table. */}
|
|
455
|
-
<Tooltip content={
|
|
482
|
+
<Tooltip content={applicationRuntimeLabel(r.members.map((member) => member.row), r.workloadClass, r.health)} delay={150}>
|
|
456
483
|
<span className={clsx('h-8 w-1 shrink-0 rounded-full', HEALTH_META[r.health].bar)} />
|
|
457
484
|
</Tooltip>
|
|
458
485
|
<ChevronRight className={clsx('h-3.5 w-3.5 shrink-0 text-theme-text-tertiary transition-transform', r.expanded && 'rotate-90')} aria-hidden />
|
|
459
486
|
<span className="truncate font-semibold text-theme-text-primary">{r.label}</span>
|
|
487
|
+
{r.workloadClass !== 'job' && <BatchSignalChip signal={firstBatchSignal(r.members.map((m) => m.row))} />}
|
|
460
488
|
<Tooltip
|
|
461
489
|
content={<AppIdentityTooltip identityKey={r.label} source={r.members[0]?.row.identity?.source} portable={r.members[0]?.row.identity?.portable} fleet={variant === 'fleet'} members={r.members.map((m) => ({ name: m.row.name, env: m.row.identity!.env, confidence: m.row.identity!.confidence, evidence: m.row.identity!.evidence }))} />}
|
|
462
490
|
delay={150}
|
|
@@ -504,7 +532,7 @@ export function ApplicationsView({ entries: allEntries, variant, onSelect, title
|
|
|
504
532
|
</span>
|
|
505
533
|
</td>
|
|
506
534
|
<td className="px-2 py-2.5"><ClassBadge workloadClass={r.workloadClass} composition={r.classComposition} /></td>
|
|
507
|
-
<td className="px-2 py-2.5"><
|
|
535
|
+
<td className="px-2 py-2.5"><ApplicationRuntimeCell apps={r.members.map((member) => member.row)} workloadClass={r.workloadClass} ready={r.ready} desired={r.desired} /></td>
|
|
508
536
|
<td className="px-2 py-2.5">
|
|
509
537
|
{r.lag ? (
|
|
510
538
|
<Tooltip content={`Promotion lag: ${r.lag} (${r.cells.filter((c) => c.version).map((c) => `${c.env}=${c.version}`).join(', ')})`} delay={150}>
|
|
@@ -531,10 +559,11 @@ export function ApplicationsView({ entries: allEntries, variant, onSelect, title
|
|
|
531
559
|
>
|
|
532
560
|
<td className={clsx('py-2.5 pr-2', r.child ? 'pl-10' : 'pl-3')}>
|
|
533
561
|
<span className="flex items-center gap-2">
|
|
534
|
-
<Tooltip content={
|
|
562
|
+
<Tooltip content={applicationRuntimeLabel([e.row], e.workloadClass, e.health)} delay={150}>
|
|
535
563
|
<span className={clsx('h-8 w-1 shrink-0 rounded-full', HEALTH_META[e.health].bar)} />
|
|
536
564
|
</Tooltip>
|
|
537
565
|
<span className="truncate font-medium text-theme-text-primary">{e.row.name}</span>
|
|
566
|
+
{e.workloadClass !== 'job' && <BatchSignalChip signal={batchSignalForApp(e.row)} />}
|
|
538
567
|
<ProvenanceBadge tier={e.row.tier} appKey={e.row.key} confidence={e.row.confidence} />
|
|
539
568
|
<CategoryChip category={e.category} addonReason={e.row.addonReason} />
|
|
540
569
|
</span>
|
|
@@ -601,7 +630,7 @@ export function ApplicationsView({ entries: allEntries, variant, onSelect, title
|
|
|
601
630
|
</>
|
|
602
631
|
)}
|
|
603
632
|
<td className="px-2 py-2.5"><ClassBadge workloadClass={e.workloadClass} composition={e.classComposition} /></td>
|
|
604
|
-
<td className="px-2 py-2.5"><
|
|
633
|
+
<td className="px-2 py-2.5"><ApplicationRuntimeCell apps={[e.row]} workloadClass={e.workloadClass} ready={e.ready} desired={e.desired} /></td>
|
|
605
634
|
<td className="px-2 py-2.5">
|
|
606
635
|
{e.variant === 'fleet' ? (
|
|
607
636
|
e.versionSkew ? (
|
|
@@ -640,3 +669,26 @@ export function ApplicationsView({ entries: allEntries, variant, onSelect, title
|
|
|
640
669
|
</div>
|
|
641
670
|
)
|
|
642
671
|
}
|
|
672
|
+
|
|
673
|
+
function firstBatchSignal(rows: AppRow[]) {
|
|
674
|
+
let best = null as ReturnType<typeof batchSignalForApp>
|
|
675
|
+
const rank = (tone: string | undefined) => {
|
|
676
|
+
switch (tone) {
|
|
677
|
+
case 'rose':
|
|
678
|
+
return 5
|
|
679
|
+
case 'amber':
|
|
680
|
+
return 4
|
|
681
|
+
case 'sky':
|
|
682
|
+
return 3
|
|
683
|
+
case 'emerald':
|
|
684
|
+
return 2
|
|
685
|
+
default:
|
|
686
|
+
return 0
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
for (const row of rows) {
|
|
690
|
+
const signal = batchSignalForApp(row)
|
|
691
|
+
if (signal && rank(signal.tone) > rank(best?.tone)) best = signal
|
|
692
|
+
}
|
|
693
|
+
return best
|
|
694
|
+
}
|
|
@@ -67,6 +67,53 @@ describe('buildSingleAppEntry', () => {
|
|
|
67
67
|
expect(e.env).toBe('staging')
|
|
68
68
|
expect(e.envInferred).toBe(false)
|
|
69
69
|
})
|
|
70
|
+
|
|
71
|
+
it('uses delivery degradation in the displayed application health', () => {
|
|
72
|
+
const e = buildSingleAppEntry(app({
|
|
73
|
+
health: 'degraded',
|
|
74
|
+
runtimeHealth: 'healthy',
|
|
75
|
+
sourceStatus: { sync: 'Synced', health: 'Degraded' },
|
|
76
|
+
}))
|
|
77
|
+
|
|
78
|
+
expect(e.health).toBe('degraded')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('preserves the latest retained batch outcome when delivery adds no degradation', () => {
|
|
82
|
+
const e = buildSingleAppEntry(app({
|
|
83
|
+
health: 'unhealthy',
|
|
84
|
+
runtimeHealth: 'unhealthy',
|
|
85
|
+
workload_class: 'job',
|
|
86
|
+
workloads: [wl({
|
|
87
|
+
kind: 'CronJob',
|
|
88
|
+
workload_class: 'job',
|
|
89
|
+
health: 'unhealthy',
|
|
90
|
+
ready: 0,
|
|
91
|
+
desired: 0,
|
|
92
|
+
batch: { retainedRuns: 2, failedRuns: 1, succeededRuns: 1, latestRunPhase: 'Succeeded' },
|
|
93
|
+
})],
|
|
94
|
+
}))
|
|
95
|
+
|
|
96
|
+
expect(e.health).toBe('healthy')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('applies delivery degradation to a batch app with a successful latest run', () => {
|
|
100
|
+
const e = buildSingleAppEntry(app({
|
|
101
|
+
health: 'unhealthy',
|
|
102
|
+
runtimeHealth: 'unhealthy',
|
|
103
|
+
workload_class: 'job',
|
|
104
|
+
sourceStatus: { sync: 'Synced', health: 'Degraded' },
|
|
105
|
+
workloads: [wl({
|
|
106
|
+
kind: 'CronJob',
|
|
107
|
+
workload_class: 'job',
|
|
108
|
+
health: 'unhealthy',
|
|
109
|
+
ready: 0,
|
|
110
|
+
desired: 0,
|
|
111
|
+
batch: { retainedRuns: 2, failedRuns: 1, succeededRuns: 1, latestRunPhase: 'Succeeded' },
|
|
112
|
+
})],
|
|
113
|
+
}))
|
|
114
|
+
|
|
115
|
+
expect(e.health).toBe('degraded')
|
|
116
|
+
})
|
|
70
117
|
})
|
|
71
118
|
|
|
72
119
|
describe('searchTextForEntry (single)', () => {
|
|
@@ -7,7 +7,7 @@ export { buildSingleAppEntry } from '../../utils/applications'
|
|
|
7
7
|
// Facet moved to the shared ui/ primitives; re-exported here for compatibility.
|
|
8
8
|
export { Facet } from '../ui/Facet'
|
|
9
9
|
export { ApplicationDetail } from './ApplicationDetail'
|
|
10
|
-
export type { ApplicationDetailProps, SelectedAppWorkload, AppIdentityInstance } from './ApplicationDetail'
|
|
10
|
+
export type { ApplicationDetailProps, SelectedAppWorkload, AppIdentityInstance, ApplicationView } from './ApplicationDetail'
|
|
11
11
|
export { CenteredEmpty } from '../ui/CenteredEmpty'
|
|
12
12
|
export { ProvenanceBadge, ClassBadge, CategoryChip, VersionInfo } from './AppChips'
|
|
13
13
|
export { ProvenanceTooltip, CategoryTooltip, VersionTooltip, AppIdentityTooltip } from './AppTooltips'
|
|
@@ -41,14 +41,15 @@ export function AuditAlerts({ findings, onViewAll }: AuditAlertsProps) {
|
|
|
41
41
|
const warnings = findings.filter(f => f.severity === 'warning').length
|
|
42
42
|
|
|
43
43
|
return (
|
|
44
|
-
<
|
|
44
|
+
<section className="rounded-lg border border-theme-border bg-theme-surface p-4 shadow-theme-sm">
|
|
45
45
|
<button
|
|
46
|
+
type="button"
|
|
46
47
|
onClick={() => setExpanded(!expanded)}
|
|
47
|
-
className="flex items-center gap-2
|
|
48
|
+
className="flex w-full items-center gap-2 rounded-md text-left transition-colors hover:text-theme-text-primary"
|
|
48
49
|
>
|
|
49
50
|
<ChevronRight className={clsx('w-4 h-4 text-theme-text-tertiary transition-transform duration-200', expanded && 'rotate-90')} />
|
|
50
51
|
<ClipboardCheck className="w-4 h-4 text-theme-text-secondary" />
|
|
51
|
-
<span className="text-sm font-
|
|
52
|
+
<span className="text-sm font-semibold text-theme-text-primary">Audit Findings</span>
|
|
52
53
|
<div className="flex items-center gap-2 ml-1">
|
|
53
54
|
{dangers > 0 && (
|
|
54
55
|
<span className={clsx('text-xs font-medium tabular-nums', SEVERITY_TEXT.error)}>{dangers} critical</span>
|
|
@@ -64,7 +65,7 @@ export function AuditAlerts({ findings, onViewAll }: AuditAlertsProps) {
|
|
|
64
65
|
style={{ gridTemplateRows: expanded ? '1fr' : '0fr' }}
|
|
65
66
|
>
|
|
66
67
|
<div className="overflow-hidden">
|
|
67
|
-
<div className="
|
|
68
|
+
<div className="flex flex-col gap-0.5 pt-3">
|
|
68
69
|
{findings.map((f, i) => {
|
|
69
70
|
const isDanger = f.severity === 'danger'
|
|
70
71
|
return (
|
|
@@ -74,17 +75,20 @@ export function AuditAlerts({ findings, onViewAll }: AuditAlertsProps) {
|
|
|
74
75
|
) : (
|
|
75
76
|
<AlertTriangle className={clsx('w-3.5 h-3.5 shrink-0 mt-0.5', SEVERITY_TEXT.warning)} />
|
|
76
77
|
)}
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
{f.category}
|
|
80
|
-
|
|
78
|
+
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-x-2 gap-y-1">
|
|
79
|
+
<span className="text-xs text-theme-text-secondary">{f.message}</span>
|
|
80
|
+
<span className={clsx('badge-sm text-[10px] shrink-0', BP_CATEGORY_BADGE[f.category] || DEFAULT_BADGE_COLOR)}>
|
|
81
|
+
{f.category}
|
|
82
|
+
</span>
|
|
83
|
+
</div>
|
|
81
84
|
</div>
|
|
82
85
|
)
|
|
83
86
|
})}
|
|
84
87
|
{onViewAll && (
|
|
85
88
|
<button
|
|
89
|
+
type="button"
|
|
86
90
|
onClick={(e) => { e.stopPropagation(); onViewAll() }}
|
|
87
|
-
className="flex items-center gap-1 text-xs text-
|
|
91
|
+
className="mt-1 flex items-center gap-1 py-1 text-xs text-accent-text transition-colors hover:text-accent"
|
|
88
92
|
>
|
|
89
93
|
View all findings
|
|
90
94
|
<ArrowRight className="w-3 h-3" />
|
|
@@ -93,6 +97,6 @@ export function AuditAlerts({ findings, onViewAll }: AuditAlertsProps) {
|
|
|
93
97
|
</div>
|
|
94
98
|
</div>
|
|
95
99
|
</div>
|
|
96
|
-
</
|
|
100
|
+
</section>
|
|
97
101
|
)
|
|
98
102
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState, type ComponentType, type ReactNode } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
3
|
import { AlertCircle, AlertOctagon, AlertTriangle, ChevronDown, ChevronRight, ExternalLink, EyeOff, Info, Layers, MoreHorizontal, Search, ShieldCheck, Wrench, X } from 'lucide-react'
|
|
4
|
-
import { CardBody, CardSection, ClusterName, EmptyState, FilterPill, DistributionBar, DistributionLegendChip, NEUTRAL_CHIP_CLASS, renderProse } from '../ui'
|
|
4
|
+
import { CardBody, CardSection, ClusterName, EmptyState, FilterPill, DistributionBar, DistributionLegendChip, Input, NEUTRAL_CHIP_CLASS, renderProse } from '../ui'
|
|
5
5
|
import { useFilterState, defineFilterSchema } from '../../filter-state'
|
|
6
6
|
import type { CheckMeta, CheckReference } from '../audit'
|
|
7
7
|
import { CHECK_SEVERITIES, CHECK_SEVERITY_RANK, type Check, type CheckSeverity, type EffectiveCheckFinding, type CheckResourceRef } from './types'
|
|
@@ -265,8 +265,7 @@ export function ChecksView({ checks, catalog, anyData, resourceHref, onResourceC
|
|
|
265
265
|
</div>
|
|
266
266
|
<div className="relative">
|
|
267
267
|
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-theme-text-tertiary" />
|
|
268
|
-
<
|
|
269
|
-
type="text"
|
|
268
|
+
<Input
|
|
270
269
|
placeholder="Search checks…"
|
|
271
270
|
value={search}
|
|
272
271
|
onChange={(e) => filters.setString('q', e.target.value)}
|
|
@@ -10,14 +10,15 @@ import {
|
|
|
10
10
|
} from '../ui/severity-tone'
|
|
11
11
|
|
|
12
12
|
// The visual language for the 4-tier Checks severity ladder. One hue per tier:
|
|
13
|
-
// red=critical, orange=high,
|
|
14
|
-
// rail top-to-bottom and severity is obvious without reading a word.
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// red=critical, orange=high, yellow=medium, neutral=low — read the queue's left
|
|
14
|
+
// rail top-to-bottom and severity is obvious without reading a word. Medium is
|
|
15
|
+
// yellow (not amber) so it sits a clear hue-step below high's orange rather than
|
|
16
|
+
// blurring into it. The color strings are shared with the Issue card via the
|
|
17
|
+
// tone module; here we only map each tier onto its tone.
|
|
17
18
|
const CHECK_SEVERITY_TONE: Record<CheckSeverity, SeverityTone> = {
|
|
18
19
|
critical: 'red',
|
|
19
20
|
high: 'orange',
|
|
20
|
-
medium: '
|
|
21
|
+
medium: 'yellow',
|
|
21
22
|
low: 'slate',
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -35,14 +36,17 @@ export const SEVERITY_LABEL: Record<CheckSeverity, string> = {
|
|
|
35
36
|
low: 'Low',
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
39
|
+
// Soft pill badge — the loud, explicit severity signal on rows + drawer header.
|
|
40
|
+
// Critical/high/low reuse the canonical Badge tones; medium takes an explicit
|
|
41
|
+
// yellow (matching its tone above) rather than the shared amber `warning` tone,
|
|
42
|
+
// so it reads a clear step below high's orange without shifting the amber
|
|
43
|
+
// `warning` badge used elsewhere (Issues, GitOps). yellow-800 on yellow-100
|
|
44
|
+
// clears WCAG for the pill text.
|
|
42
45
|
export const SEVERITY_BADGE_CLASS: Record<CheckSeverity, string> = {
|
|
43
46
|
critical: sev.error,
|
|
44
47
|
high: sev.alert,
|
|
45
|
-
medium:
|
|
48
|
+
medium:
|
|
49
|
+
'bg-yellow-100 text-yellow-800 border-yellow-300 dark:bg-yellow-950/50 dark:text-yellow-400 dark:border-yellow-700/40',
|
|
46
50
|
low: sev.neutral,
|
|
47
51
|
}
|
|
48
52
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from 'react'
|
|
10
10
|
import { ChevronDown, Check, FolderOpen, Loader2, Search, Server, X } from 'lucide-react'
|
|
11
11
|
import { ClusterName } from '../ui/ClusterName'
|
|
12
|
+
import { Input } from '../ui/Input'
|
|
12
13
|
import { MiddleEllipsis } from '../ui/MiddleEllipsis'
|
|
13
14
|
import { StatusDot, type StatusTone } from '../ui/status-tone'
|
|
14
15
|
|
|
@@ -269,9 +270,8 @@ export const ClusterSwitcher = forwardRef<ClusterSwitcherHandle, ClusterSwitcher
|
|
|
269
270
|
<div className="p-2 border-b border-theme-border">
|
|
270
271
|
<div className="relative">
|
|
271
272
|
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-theme-text-tertiary" />
|
|
272
|
-
<
|
|
273
|
+
<Input
|
|
273
274
|
ref={searchInputRef}
|
|
274
|
-
type="text"
|
|
275
275
|
value={search}
|
|
276
276
|
onChange={e => setSearch(e.target.value)}
|
|
277
277
|
onKeyDown={onSearchKeyDown}
|
|
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
|
|
2
2
|
import { clsx } from 'clsx'
|
|
3
3
|
import { GitCompare, Search, X } from 'lucide-react'
|
|
4
4
|
import { DialogPortal } from '../ui/DialogPortal'
|
|
5
|
+
import { Input } from '../ui/Input'
|
|
5
6
|
import { pluralToKind } from '../../utils/navigation'
|
|
6
7
|
import type { CompareResourceRef } from './ResourceCompareView'
|
|
7
8
|
import { sortCandidates, filterCandidates } from './sort'
|
|
@@ -120,9 +121,8 @@ export function CompareResourcePicker({
|
|
|
120
121
|
</div>
|
|
121
122
|
<div className="relative">
|
|
122
123
|
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-theme-text-tertiary pointer-events-none" />
|
|
123
|
-
<
|
|
124
|
+
<Input
|
|
124
125
|
autoFocus
|
|
125
|
-
type="text"
|
|
126
126
|
value={query}
|
|
127
127
|
onChange={e => setQuery(e.target.value)}
|
|
128
128
|
onKeyDown={handleKeyDown}
|
|
@@ -77,6 +77,7 @@ export interface ArgoActionHandlers {
|
|
|
77
77
|
autoSyncEnabled: boolean
|
|
78
78
|
// isRunning: caller derives from status.operationState.phase === 'Running'.
|
|
79
79
|
isRunning: boolean
|
|
80
|
+
operationInProgress?: boolean
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
export interface FluxActionHandlers {
|
|
@@ -158,6 +159,9 @@ export interface GitOpsDetailLayoutProps {
|
|
|
158
159
|
// hub-web and OSS can wire their own data sources without the layout
|
|
159
160
|
// knowing.
|
|
160
161
|
renderTabBody: (ctx: { tab: GitOpsDetailTab; fullscreen: boolean }) => ReactNode
|
|
162
|
+
// Optional host slot: inline Git commit metadata next to the status strip's
|
|
163
|
+
// latest revision (author, signature). Wired by the host to a data loader.
|
|
164
|
+
renderRevisionMeta?: (revision: string) => ReactNode
|
|
161
165
|
// Optional: top-right tab-bar accessory (Clear filters + Fullscreen
|
|
162
166
|
// buttons in OSS, can be anything else in hub-web).
|
|
163
167
|
renderTabBarAccessory?: (ctx: { tab: GitOpsDetailTab; fullscreen: boolean }) => ReactNode
|
|
@@ -228,6 +232,7 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
228
232
|
fullscreen,
|
|
229
233
|
onToggleFullscreen,
|
|
230
234
|
renderTabBody,
|
|
235
|
+
renderRevisionMeta,
|
|
231
236
|
renderTabBarAccessory,
|
|
232
237
|
renderTabBarCounts,
|
|
233
238
|
resourceLoading,
|
|
@@ -252,6 +257,7 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
252
257
|
}, [identity.name, manageDocumentTitle, documentTitleSuffix])
|
|
253
258
|
|
|
254
259
|
const effectiveSuspended = status?.suspended ?? false
|
|
260
|
+
const argoOperationInProgress = argo?.operationInProgress ?? argo?.isRunning ?? false
|
|
255
261
|
// graphFullscreen hides everything chrome-side; the body region expands.
|
|
256
262
|
// Mirrors the OSS GitOpsDetailView shell behavior so the visual chrome
|
|
257
263
|
// class set carries through.
|
|
@@ -344,8 +350,12 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
344
350
|
icon={ArrowDownUp}
|
|
345
351
|
loading={argo.syncing}
|
|
346
352
|
onClick={argo.onSyncRequested}
|
|
347
|
-
disabled={effectiveSuspended || terminating}
|
|
348
|
-
disabledReason={terminating
|
|
353
|
+
disabled={effectiveSuspended || terminating || argoOperationInProgress}
|
|
354
|
+
disabledReason={terminating
|
|
355
|
+
? terminatingActionTooltip
|
|
356
|
+
: argoOperationInProgress
|
|
357
|
+
? 'Wait for the current Argo operation to finish.'
|
|
358
|
+
: undefined}
|
|
349
359
|
primary
|
|
350
360
|
/>
|
|
351
361
|
<ActionButton
|
|
@@ -447,7 +457,7 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
447
457
|
)}
|
|
448
458
|
{!fullscreen && (
|
|
449
459
|
<>
|
|
450
|
-
<GitOpsStatusStrip insight={insight ?? undefined} loading={insightLoading} />
|
|
460
|
+
<GitOpsStatusStrip insight={insight ?? undefined} loading={insightLoading} renderRevisionMeta={renderRevisionMeta} />
|
|
451
461
|
<GitOpsIssuesBand
|
|
452
462
|
issues={insight?.issues}
|
|
453
463
|
terminating={terminating}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CheckCircle2, CircleDot, GitBranch, HeartPulse, LayoutGrid, List, Search } from 'lucide-react'
|
|
2
2
|
|
|
3
3
|
import { GitOpsFacetButton, GitOpsFilterSection } from './GitOpsTableView'
|
|
4
|
+
import { Input } from '../ui/Input'
|
|
4
5
|
import type { GitOpsResourceTree } from '../../types/gitops-tree'
|
|
5
6
|
import type { GitOpsTreePreset } from './tree'
|
|
6
7
|
|
|
@@ -79,7 +80,7 @@ export function GitOpsGraphFilterRail({
|
|
|
79
80
|
<div className="border-b border-theme-border px-3 py-3">
|
|
80
81
|
<div className="relative">
|
|
81
82
|
<Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-theme-text-tertiary" />
|
|
82
|
-
<
|
|
83
|
+
<Input
|
|
83
84
|
value={search}
|
|
84
85
|
onChange={(event) => onSearchChange(event.target.value)}
|
|
85
86
|
placeholder="Filter resources..."
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
|
|
27
27
|
import { HealthStatusBadge, SyncStatusBadge } from './GitOpsStatusBadge'
|
|
28
28
|
import { Tooltip } from '../ui/Tooltip'
|
|
29
|
+
import { Input } from '../ui/Input'
|
|
29
30
|
import { PageHeader } from '../ui/PageHeader'
|
|
30
31
|
import { SummaryTile, type SummaryTone } from '../ui/SummaryTile'
|
|
31
32
|
import { FacetSection, FacetButton } from '../ui/Facet'
|
|
@@ -34,7 +35,7 @@ import { DistributionBar } from '../ui/DistributionBar'
|
|
|
34
35
|
import { RowActionMenu, type RowActionItem } from '../ui/RowActionMenu'
|
|
35
36
|
import { BoardTableSkeleton, BoardRailSkeleton } from '../ui/BoardSkeleton'
|
|
36
37
|
import { getGitOpsResourceStatus } from './detail-helpers'
|
|
37
|
-
import { isArgoSuspendedByRadar } from '../resources/resource-utils-argo'
|
|
38
|
+
import { isArgoOperationInProgress, isArgoSuspendedByRadar } from '../resources/resource-utils-argo'
|
|
38
39
|
import { toggleSet } from './GitOpsGraphFilterRail'
|
|
39
40
|
import { useFilterState, defineFilterSchema } from '../../filter-state'
|
|
40
41
|
import { parseContextName } from '../../utils/context-name'
|
|
@@ -687,7 +688,7 @@ export function GitOpsTableView({
|
|
|
687
688
|
<div className="mt-3 flex flex-wrap items-center gap-2">
|
|
688
689
|
<div className="relative w-full max-w-md">
|
|
689
690
|
<Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-theme-text-tertiary" />
|
|
690
|
-
<
|
|
691
|
+
<Input
|
|
691
692
|
ref={searchInputRef}
|
|
692
693
|
value={search}
|
|
693
694
|
onChange={(e) => filters.setString('q', e.target.value)}
|
|
@@ -1129,8 +1130,7 @@ function LabelsDropdown({
|
|
|
1129
1130
|
<div className="flex items-center gap-2">
|
|
1130
1131
|
<div className="relative flex-1">
|
|
1131
1132
|
<Search className="pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-theme-text-tertiary" />
|
|
1132
|
-
<
|
|
1133
|
-
type="text"
|
|
1133
|
+
<Input
|
|
1134
1134
|
value={search}
|
|
1135
1135
|
onChange={(e) => onSearchChange(e.target.value)}
|
|
1136
1136
|
placeholder="Search labels..."
|
|
@@ -1358,13 +1358,20 @@ function buildRowActionItems(
|
|
|
1358
1358
|
const items: RowActionItem[] = []
|
|
1359
1359
|
|
|
1360
1360
|
if (row.tool === 'argo') {
|
|
1361
|
+
const operationInProgress = isArgoOperationInProgress(row.raw)
|
|
1361
1362
|
items.push({
|
|
1362
1363
|
key: 'sync',
|
|
1363
1364
|
label: 'Sync...',
|
|
1364
1365
|
icon: ArrowDownUp,
|
|
1365
1366
|
onClick: () => onAction(row, 'sync'),
|
|
1366
|
-
disabled: suspended || terminating,
|
|
1367
|
-
disabledReason: terminating
|
|
1367
|
+
disabled: suspended || terminating || operationInProgress,
|
|
1368
|
+
disabledReason: terminating
|
|
1369
|
+
? terminatingReason
|
|
1370
|
+
: suspended
|
|
1371
|
+
? suspendedReason
|
|
1372
|
+
: operationInProgress
|
|
1373
|
+
? 'Wait for the current Argo operation to finish.'
|
|
1374
|
+
: undefined,
|
|
1368
1375
|
pending: isPending('sync'),
|
|
1369
1376
|
})
|
|
1370
1377
|
// Refresh / Hard refresh are read-style verbs — they re-read Git and
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { Package, ExternalLink } from 'lucide-react'
|
|
3
3
|
import { clsx } from 'clsx'
|
|
4
4
|
import type { ManagedResource, GitOpsHealthStatus } from '../../types/gitops'
|
|
5
5
|
import { groupManagedResourcesByKind } from '../../types/gitops'
|
|
6
6
|
import { pluralize } from '../../utils/pluralize'
|
|
7
|
+
import { Collapse, CollapseChevron } from '../ui/Collapse'
|
|
7
8
|
|
|
8
9
|
interface ManagedResourcesListProps {
|
|
9
10
|
resources: ManagedResource[]
|
|
@@ -45,18 +46,14 @@ export function ManagedResourcesList({
|
|
|
45
46
|
onClick={() => setExpanded(!expanded)}
|
|
46
47
|
className="flex items-center gap-2 w-full text-left mb-2 hover:text-theme-text-primary transition-colors"
|
|
47
48
|
>
|
|
48
|
-
{expanded
|
|
49
|
-
<ChevronDown className="w-4 h-4 text-theme-text-tertiary" />
|
|
50
|
-
) : (
|
|
51
|
-
<ChevronRight className="w-4 h-4 text-theme-text-tertiary" />
|
|
52
|
-
)}
|
|
49
|
+
<CollapseChevron open={expanded} className="w-4 h-4" />
|
|
53
50
|
<Package className="w-4 h-4 text-theme-text-secondary" />
|
|
54
51
|
<span className="text-sm font-medium text-theme-text-secondary">
|
|
55
52
|
{title} ({resources.length})
|
|
56
53
|
</span>
|
|
57
54
|
</button>
|
|
58
55
|
|
|
59
|
-
{expanded
|
|
56
|
+
<Collapse open={expanded} mountLazily>
|
|
60
57
|
<div className="pl-6 space-y-3">
|
|
61
58
|
{sortedGroups.map(([kind, kindResources]) => (
|
|
62
59
|
<ResourceKindGroup
|
|
@@ -77,7 +74,7 @@ export function ManagedResourcesList({
|
|
|
77
74
|
</button>
|
|
78
75
|
)}
|
|
79
76
|
</div>
|
|
80
|
-
|
|
77
|
+
</Collapse>
|
|
81
78
|
</div>
|
|
82
79
|
)
|
|
83
80
|
}
|
|
@@ -98,12 +95,12 @@ function ResourceKindGroup({ kind, resources, onResourceClick, showHealth }: Res
|
|
|
98
95
|
onClick={() => setExpanded(!expanded)}
|
|
99
96
|
className="flex items-center gap-1.5 text-xs text-theme-text-tertiary hover:text-theme-text-secondary transition-colors mb-1"
|
|
100
97
|
>
|
|
101
|
-
|
|
98
|
+
<CollapseChevron open={expanded} className="w-3 h-3" />
|
|
102
99
|
<span className="font-medium">{kind}</span>
|
|
103
100
|
<span className="text-theme-text-tertiary">({resources.length})</span>
|
|
104
101
|
</button>
|
|
105
102
|
|
|
106
|
-
{expanded
|
|
103
|
+
<Collapse open={expanded} mountLazily>
|
|
107
104
|
<div className="ml-4 space-y-0.5">
|
|
108
105
|
{resources.map((resource, idx) => (
|
|
109
106
|
<ResourceItem
|
|
@@ -114,7 +111,7 @@ function ResourceKindGroup({ kind, resources, onResourceClick, showHealth }: Res
|
|
|
114
111
|
/>
|
|
115
112
|
))}
|
|
116
113
|
</div>
|
|
117
|
-
|
|
114
|
+
</Collapse>
|
|
118
115
|
</div>
|
|
119
116
|
)
|
|
120
117
|
}
|