@skyhook-io/k8s-ui 1.18.2 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -2
- package/src/components/applications/ApplicationDetail.tsx +8 -2
- package/src/components/charts/AreaChart.test.tsx +39 -1
- package/src/components/charts/AreaChart.tsx +32 -13
- package/src/components/charts/axis.test.ts +17 -1
- package/src/components/charts/axis.ts +13 -0
- package/src/components/charts/format.test.ts +159 -2
- package/src/components/charts/format.ts +118 -0
- package/src/components/cluster-switcher/ClusterSwitcher.test.tsx +54 -0
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +7 -3
- package/src/components/compare/ResourceCompareView.test.tsx +54 -0
- package/src/components/compare/ResourceCompareView.tsx +35 -12
- package/src/components/gitops/GitOpsDetailLayout.test.tsx +16 -0
- package/src/components/gitops/GitOpsDetailLayout.tsx +54 -7
- package/src/components/gitops/GitOpsHealthSourceNotice.tsx +1 -1
- package/src/components/gitops/GitOpsTableView.tsx +26 -7
- package/src/components/gitops/RollbackDialog.test.tsx +80 -0
- package/src/components/gitops/RollbackDialog.tsx +25 -6
- package/src/components/gitops/SyncOptionsDialog.render.test.tsx +374 -0
- package/src/components/gitops/SyncOptionsDialog.test.ts +54 -1
- package/src/components/gitops/SyncOptionsDialog.tsx +179 -34
- package/src/components/gitops/applicationset-row.test.ts +12 -0
- package/src/components/gitops/argo-radar-upgrade.test.tsx +369 -0
- package/src/components/gitops/detail-helpers.test.ts +8 -2
- package/src/components/gitops/detail-helpers.ts +9 -11
- package/src/components/gitops/index.ts +1 -1
- package/src/components/gitops/insights/ArgoResourceDiff.test.tsx +32 -0
- package/src/components/gitops/insights/GitOpsActivityInsightView.test.tsx +55 -0
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +94 -46
- package/src/components/gitops/insights/GitOpsIssuesBand.test.tsx +108 -0
- package/src/components/gitops/insights/GitOpsRollbackInsightView.test.tsx +73 -0
- package/src/components/gitops/insights/GitOpsSyncWindowInsightView.test.tsx +29 -0
- package/src/components/gitops/insights/insights-helpers.test.ts +65 -0
- package/src/components/gitops/insights/insights-helpers.ts +17 -3
- package/src/components/issues/IssueGuidance.test.tsx +190 -0
- package/src/components/issues/IssueGuidance.tsx +48 -0
- package/src/components/issues/IssuesView.tsx +15 -3
- package/src/components/issues/ResourceIssuesSection.tsx +3 -1
- package/src/components/issues/diagnostic.ts +14 -1
- package/src/components/issues/index.ts +1 -0
- package/src/components/issues/severity.ts +15 -0
- package/src/components/issues/types.ts +10 -0
- package/src/components/kueue/AdmissionPath.tsx +223 -0
- package/src/components/kueue/QueueEvidence.tsx +148 -0
- package/src/components/kueue/constants.ts +1 -0
- package/src/components/kueue/eligibility.ts +43 -0
- package/src/components/kueue/flavors-tried.ts +79 -0
- package/src/components/kueue/helpers.test.ts +105 -0
- package/src/components/kueue/policy.ts +100 -0
- package/src/components/kueue/queue-detail.test.tsx +317 -0
- package/src/components/kueue/queue-status.ts +42 -0
- package/src/components/kueue/related-read.ts +48 -0
- package/src/components/kueue/testdata/kueue-v0.18-policy-enums.json +89 -0
- package/src/components/logs/StructuredLogLine.test.tsx +22 -0
- package/src/components/logs/StructuredLogLine.tsx +8 -3
- package/src/components/resources/KueueAdmissionSection.test.tsx +117 -0
- package/src/components/resources/KueueAdmissionSection.tsx +51 -12
- package/src/components/resources/KueuePendingPosition.test.tsx +97 -0
- package/src/components/resources/KueuePendingPosition.tsx +91 -0
- package/src/components/resources/ResourcesSidebar.test.tsx +8 -0
- package/src/components/resources/ResourcesSidebar.tsx +3 -2
- package/src/components/resources/ResourcesView.crd.test.tsx +28 -0
- package/src/components/resources/ResourcesView.node-pods.test.tsx +25 -0
- package/src/components/resources/ResourcesView.strimzi.test.tsx +37 -0
- package/src/components/resources/ResourcesView.tsx +50 -3
- package/src/components/resources/curated-column-ownership.test.ts +3 -3
- package/src/components/resources/generic-status.ts +7 -0
- package/src/components/resources/get-pod-phase-display.test.ts +70 -0
- package/src/components/resources/renderers/ArgoApplicationRenderer.test.tsx +54 -1
- package/src/components/resources/renderers/ArgoApplicationRenderer.tsx +7 -8
- package/src/components/resources/renderers/CRDSchemaBrowser.test.tsx +105 -0
- package/src/components/resources/renderers/CRDSchemaBrowser.tsx +134 -0
- package/src/components/resources/renderers/CustomResourceDefinitionRenderer.test.tsx +106 -0
- package/src/components/resources/renderers/CustomResourceDefinitionRenderer.tsx +152 -0
- package/src/components/resources/renderers/JobRenderer.test.tsx +35 -0
- package/src/components/resources/renderers/JobRenderer.tsx +7 -2
- package/src/components/resources/renderers/KueueQueueRenderers.test.tsx +5 -5
- package/src/components/resources/renderers/KueueQueueRenderers.tsx +250 -170
- package/src/components/resources/renderers/KueueQueueWorkloads.test.tsx +189 -0
- package/src/components/resources/renderers/KueueQueueWorkloads.tsx +460 -0
- package/src/components/resources/renderers/KueueWorkloadRenderer.test.tsx +1 -1
- package/src/components/resources/renderers/KueueWorkloadRenderer.tsx +23 -52
- package/src/components/resources/renderers/OpenTelemetryRenderers.tsx +11 -6
- package/src/components/resources/renderers/RayClusterRenderer.test.tsx +39 -0
- package/src/components/resources/renderers/RayClusterRenderer.tsx +23 -5
- package/src/components/resources/renderers/ServiceAccountRenderer.test.tsx +83 -0
- package/src/components/resources/renderers/ServiceAccountRenderer.tsx +15 -10
- package/src/components/resources/renderers/StrimziConnectorRenderer.tsx +61 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.test.tsx +32 -0
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +9 -3
- package/src/components/resources/renderers/crd-cells.test.tsx +30 -0
- package/src/components/resources/renderers/crd-cells.tsx +55 -0
- package/src/components/resources/renderers/crd-schema-parts.test.tsx +59 -0
- package/src/components/resources/renderers/crd-schema-parts.tsx +121 -0
- package/src/components/resources/renderers/index.ts +6 -0
- package/src/components/resources/renderers/kueue-cells.tsx +5 -0
- package/src/components/resources/resource-utils-argo.test.ts +94 -1
- package/src/components/resources/resource-utils-argo.ts +84 -5
- package/src/components/resources/resource-utils-crd.test.ts +32 -0
- package/src/components/resources/resource-utils-crd.ts +26 -0
- package/src/components/resources/resource-utils-gpu-ecosystem.test.ts +12 -0
- package/src/components/resources/resource-utils-kueue-admission.test.ts +26 -0
- package/src/components/resources/resource-utils-kueue.test.ts +15 -0
- package/src/components/resources/resource-utils-kueue.ts +137 -4
- package/src/components/resources/resource-utils-opentelemetry.test.tsx +9 -1
- package/src/components/resources/resource-utils-opentelemetry.ts +1 -1
- package/src/components/resources/resource-utils-ray.test.ts +42 -1
- package/src/components/resources/resource-utils-ray.ts +38 -0
- package/src/components/resources/resource-utils-strimzi.test.tsx +118 -0
- package/src/components/resources/resource-utils-strimzi.ts +35 -0
- package/src/components/resources/resource-utils.ts +45 -1
- package/src/components/shared/ResourceActionsBar.tsx +83 -25
- package/src/components/shared/ResourceRendererDispatch.test.tsx +37 -3
- package/src/components/shared/ResourceRendererDispatch.tsx +40 -7
- package/src/components/topology/K8sResourceNode.test.ts +14 -0
- package/src/components/topology/K8sResourceNode.tsx +18 -1
- package/src/components/topology/TopologyFilterSidebar.tsx +1 -0
- package/src/components/topology/TopologyGraph.tsx +96 -94
- package/src/components/topology/layout.ts +3 -1
- package/src/components/topology/pod-group-expansion.test.ts +38 -0
- package/src/components/ui/Badge.tsx +1 -0
- package/src/components/ui/RadarUpgradeNote.tsx +8 -0
- package/src/components/ui/drawer-components.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/workload/PodList.tsx +35 -9
- package/src/components/workload/WorkloadView.spec.test.tsx +41 -1
- package/src/components/workload/WorkloadView.tsx +61 -8
- package/src/components/workload/workload-logs-availability.test.ts +7 -0
- package/src/types/core.ts +41 -0
- package/src/types/gitops-insights.ts +19 -2
- package/src/types/gitops-tree.ts +2 -2
- package/src/types/kueue-queues.ts +84 -0
- package/src/types/read-coverage.ts +2 -1
- package/src/types/scheduling.ts +72 -0
- package/src/utils/animation.test.ts +38 -0
- package/src/utils/animation.ts +34 -0
- package/src/utils/api-resources-crd.test.ts +11 -0
- package/src/utils/api-resources.ts +5 -1
- package/src/utils/applications.test.ts +15 -1
- package/src/utils/applications.ts +26 -1
- package/src/utils/crd-schema.test.ts +107 -0
- package/src/utils/crd-schema.ts +201 -0
- package/src/utils/format.test.ts +37 -1
- package/src/utils/format.ts +28 -80
- package/src/utils/gitops-owner.test.ts +28 -2
- package/src/utils/gitops-owner.ts +19 -0
- package/src/utils/kueue-queue-workloads.ts +19 -0
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/topology-neighborhood.test.ts +90 -1
- package/src/utils/topology-neighborhood.ts +55 -3
- package/src/utils/workload-history-notes.test.ts +76 -0
- package/src/utils/workload-history-notes.ts +93 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import { ResourceCompareView, type ResourceCompareViewProps } from './ResourceCompareView'
|
|
5
|
+
|
|
6
|
+
vi.mock('../ui/YamlEditor', () => ({ YamlDiffEditor: () => null }))
|
|
7
|
+
|
|
8
|
+
const checkout = { kind: 'Deployment', namespace: 'shop', name: 'checkout' }
|
|
9
|
+
|
|
10
|
+
function header(props: Partial<ResourceCompareViewProps>) {
|
|
11
|
+
const html = renderToStaticMarkup(
|
|
12
|
+
<ResourceCompareView a={checkout} b={checkout} aData={{}} bData={{}} onSwap={() => {}} onClose={() => {}} {...props} />,
|
|
13
|
+
)
|
|
14
|
+
// Tags split the name from its namespace, so compare the text a reader sees.
|
|
15
|
+
const host = document.createElement('div')
|
|
16
|
+
host.innerHTML = html
|
|
17
|
+
return host.textContent ?? ''
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('ResourceCompareView sides', () => {
|
|
21
|
+
it('names the resource once and tells the sides apart by where they run', () => {
|
|
22
|
+
const text = header({ aSubtitle: 'payments-eu', bSubtitle: 'payments-us' })
|
|
23
|
+
expect(text.split('shop/checkout')).toHaveLength(2)
|
|
24
|
+
expect(text).toContain('payments-eu')
|
|
25
|
+
expect(text).toContain('payments-us')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('keeps a cut-off location readable on hover', () => {
|
|
29
|
+
const html = renderToStaticMarkup(
|
|
30
|
+
<ResourceCompareView a={checkout} b={checkout} aData={{}} bData={{}} onSwap={() => {}} onClose={() => {}} aSubtitle="payments-eu" bSubtitle="payments-us" />,
|
|
31
|
+
)
|
|
32
|
+
expect(html).toContain('title="payments-eu"')
|
|
33
|
+
expect(html).toContain('title="payments-us"')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('names both sides when they are different resources', () => {
|
|
37
|
+
const text = header({ b: { ...checkout, name: 'billing' }, aSubtitle: 'payments-eu', bSubtitle: 'payments-eu' })
|
|
38
|
+
expect(text).toContain('shop/checkout')
|
|
39
|
+
expect(text).toContain('shop/billing')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('keeps names when the same kind and name come from different API groups', () => {
|
|
43
|
+
const text = header({ a: { ...checkout, group: 'apps' }, b: { ...checkout, group: 'example.com' }, aSubtitle: 'eu', bSubtitle: 'us' })
|
|
44
|
+
expect(text.split('shop/checkout')).toHaveLength(3)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('keeps names when both sides come from the same place', () => {
|
|
48
|
+
expect(header({ aSubtitle: 'payments-eu', bSubtitle: 'payments-eu' }).split('shop/checkout')).toHaveLength(3)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('keeps names with no location to show', () => {
|
|
52
|
+
expect(header({}).split('shop/checkout')).toHaveLength(3)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -59,12 +59,15 @@ function ResourcePill({
|
|
|
59
59
|
error,
|
|
60
60
|
onChange,
|
|
61
61
|
subtitle,
|
|
62
|
+
subtitleOnly,
|
|
62
63
|
}: {
|
|
63
64
|
resource: CompareResourceRef
|
|
64
65
|
side: CompareSide
|
|
65
66
|
error?: string
|
|
66
67
|
onChange?: () => void
|
|
67
68
|
subtitle?: string
|
|
69
|
+
/** Both sides are the same resource, so where it runs is all that tells them apart. */
|
|
70
|
+
subtitleOnly?: boolean
|
|
68
71
|
}) {
|
|
69
72
|
const tones = SIDE_TONES[side]
|
|
70
73
|
const errTone = 'border-red-400/50 bg-red-500/10'
|
|
@@ -82,13 +85,19 @@ function ResourcePill({
|
|
|
82
85
|
{label}
|
|
83
86
|
</span>
|
|
84
87
|
{error && <AlertTriangle className="w-3 h-3 text-red-400 shrink-0" aria-hidden />}
|
|
85
|
-
<span className={clsx('truncate min-w-0', error ? 'text-red-300' : 'text-theme-text-primary')}>
|
|
86
|
-
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
<span className={clsx('truncate min-w-0', error ? 'text-red-300' : 'text-theme-text-primary')} title={subtitleOnly ? subtitle : undefined}>
|
|
89
|
+
{subtitleOnly ? (
|
|
90
|
+
subtitle
|
|
91
|
+
) : (
|
|
92
|
+
<>
|
|
93
|
+
{resource.namespace && <span className="opacity-60">{resource.namespace}/</span>}
|
|
94
|
+
{resource.name}
|
|
95
|
+
{subtitle && (
|
|
96
|
+
<span className="opacity-60 ml-1" aria-label={`Source: ${subtitle}`}>
|
|
97
|
+
· {subtitle}
|
|
98
|
+
</span>
|
|
99
|
+
)}
|
|
100
|
+
</>
|
|
92
101
|
)}
|
|
93
102
|
</span>
|
|
94
103
|
{onChange && (
|
|
@@ -144,6 +153,10 @@ export function ResourceCompareView({
|
|
|
144
153
|
// blank the editor. When the A/B identity changes (navigating to a
|
|
145
154
|
// new pair on the same route), the latch resets so the loader shows
|
|
146
155
|
// again until the new fetches settle.
|
|
156
|
+
// The same resource from two places: say what it is once, up top, so the pills
|
|
157
|
+
// can show the one thing that differs. Long names no longer push that out of view.
|
|
158
|
+
const sameResource = a.kind === b.kind && a.group === b.group && a.namespace === b.namespace && a.name === b.name &&
|
|
159
|
+
Boolean(aSubtitle && bSubtitle) && aSubtitle !== bSubtitle
|
|
147
160
|
const settleKey = `${a.kind}|${a.namespace}|${a.name}|${a.group ?? ''}|${aSubtitle ?? ''}` +
|
|
148
161
|
`|${b.kind}|${b.namespace}|${b.name}|${b.group ?? ''}|${bSubtitle ?? ''}`
|
|
149
162
|
const settleKeyRef = useRef(settleKey)
|
|
@@ -160,15 +173,25 @@ export function ResourceCompareView({
|
|
|
160
173
|
<div className="flex-1 min-w-0 flex flex-col h-full bg-theme-base">
|
|
161
174
|
<div className="h-0.5 w-full bg-gradient-to-r from-blue-400/70 via-skyhook-400/40 to-emerald-400/70" />
|
|
162
175
|
|
|
163
|
-
<div className="flex items-center gap-3 px-4 py-2.5 border-b border-theme-border bg-theme-surface">
|
|
176
|
+
<div className="flex flex-wrap items-center gap-x-3 gap-y-2 px-4 py-2.5 border-b border-theme-border bg-theme-surface">
|
|
164
177
|
<GitCompare className="w-5 h-5 text-skyhook-400 shrink-0" />
|
|
165
|
-
<h2 className=
|
|
178
|
+
<h2 className={clsx('text-sm font-semibold text-theme-text-primary whitespace-nowrap', sameResource ? 'min-w-0 max-w-[30%] truncate' : 'shrink-0')}>
|
|
166
179
|
Compare <span className="text-theme-text-tertiary mx-0.5">·</span>{' '}
|
|
167
180
|
<span className="text-theme-text-secondary font-medium">{kindLabel}</span>
|
|
181
|
+
{sameResource && (
|
|
182
|
+
<>
|
|
183
|
+
<span className="text-theme-text-tertiary mx-0.5">·</span>{' '}
|
|
184
|
+
<span className="text-theme-text-secondary font-medium" title={`${a.namespace}/${a.name}`}>
|
|
185
|
+
{a.namespace && <span className="opacity-60">{a.namespace}/</span>}
|
|
186
|
+
{a.name}
|
|
187
|
+
</span>
|
|
188
|
+
</>
|
|
189
|
+
)}
|
|
168
190
|
</h2>
|
|
169
191
|
|
|
170
|
-
|
|
171
|
-
|
|
192
|
+
{/* The row wraps before the sides get too narrow to tell apart. */}
|
|
193
|
+
<div className="flex items-center gap-2 min-w-72 flex-1">
|
|
194
|
+
<ResourcePill resource={a} side="a" error={aError} onChange={onChangeA} subtitle={aSubtitle} subtitleOnly={sameResource} />
|
|
172
195
|
<Tooltip content="Swap A and B">
|
|
173
196
|
<button
|
|
174
197
|
onClick={onSwap}
|
|
@@ -177,7 +200,7 @@ export function ResourceCompareView({
|
|
|
177
200
|
<ArrowLeftRight className="w-3.5 h-3.5" />
|
|
178
201
|
</button>
|
|
179
202
|
</Tooltip>
|
|
180
|
-
<ResourcePill resource={b} side="b" error={bError} onChange={onChangeB} subtitle={bSubtitle} />
|
|
203
|
+
<ResourcePill resource={b} side="b" error={bError} onChange={onChangeB} subtitle={bSubtitle} subtitleOnly={sameResource} />
|
|
181
204
|
</div>
|
|
182
205
|
|
|
183
206
|
<div className="flex items-center gap-1 shrink-0">
|
|
@@ -22,6 +22,21 @@ const shell: Omit<GitOpsDetailLayoutProps, 'identity' | 'isArgoApp'> = {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
describe('GitOpsDetailLayout', () => {
|
|
25
|
+
it.each([false, true])('shows tree limits without insights, including fullscreen=%s', (fullscreen) => {
|
|
26
|
+
const warning = 'Some generated resources could not be checked; the resource tree may be incomplete.'
|
|
27
|
+
const html = renderToString(
|
|
28
|
+
<GitOpsDetailLayout
|
|
29
|
+
{...shell}
|
|
30
|
+
identity={{ kind: 'applications', group: 'argoproj.io', namespace: 'shop', name: 'wordpress', kindLabel: 'Application' }}
|
|
31
|
+
isArgoApp
|
|
32
|
+
fullscreen={fullscreen}
|
|
33
|
+
warnings={[warning, warning]}
|
|
34
|
+
/>,
|
|
35
|
+
)
|
|
36
|
+
expect(html).toContain('Limited resource view')
|
|
37
|
+
expect(html.split(warning)).toHaveLength(2)
|
|
38
|
+
})
|
|
39
|
+
|
|
25
40
|
it('shows the tool chip, facts and Argo operations for an Argo CD Application', () => {
|
|
26
41
|
const html = renderToString(
|
|
27
42
|
<GitOpsDetailLayout
|
|
@@ -39,6 +54,7 @@ describe('GitOpsDetailLayout', () => {
|
|
|
39
54
|
expect(html).toContain('ArgoCD')
|
|
40
55
|
expect(html).toContain('Project')
|
|
41
56
|
expect(html).toContain('Sync…')
|
|
57
|
+
expect(html).not.toContain('Limited resource view')
|
|
42
58
|
})
|
|
43
59
|
|
|
44
60
|
// A root that is not a GitOps resource gets one state: its name and why
|
|
@@ -2,12 +2,28 @@ import { useEffect, type ComponentType, type ReactNode } from 'react'
|
|
|
2
2
|
import { ArrowDownUp, Clock3, GitBranch, GitCommit, Loader2, Pause, Play, RefreshCw, Settings, Trash2, XCircle, Zap } from 'lucide-react'
|
|
3
3
|
import { Collapse, CollapseChevron, useDisclosure } from '../ui/Collapse'
|
|
4
4
|
import { PaneLoader } from '../ui/PaneLoader'
|
|
5
|
+
import { AlertBanner } from '../ui/drawer-components'
|
|
5
6
|
|
|
6
7
|
import { HealthStatusBadge, SyncStatusBadge } from './GitOpsStatusBadge'
|
|
7
8
|
import { GitOpsIssuesBand, GitOpsStatusStrip } from './insights'
|
|
8
9
|
import { GitOpsHealthSourceNotice } from './GitOpsHealthSourceNotice'
|
|
9
10
|
import { Tooltip } from '../ui/Tooltip'
|
|
11
|
+
import { RadarUpgradeNote, radarUpgradeNoteText } from '../ui/RadarUpgradeNote'
|
|
10
12
|
import type { GitOpsHealthStatus, GitOpsInsight, GitOpsIssue, GitOpsRemediation, SyncStatus } from '../../types'
|
|
13
|
+
import type { RadarUpgradeRequirement } from '../../types/fetch-error'
|
|
14
|
+
|
|
15
|
+
function argoAutoSyncControllerNote(controller: ArgoActionHandlers['autoSyncController']): string {
|
|
16
|
+
if (!controller) return ''
|
|
17
|
+
return controller.kind === 'ApplicationSet'
|
|
18
|
+
? ` ApplicationSet ${controller.name} can turn it back on; to keep it off, change the ApplicationSet's template.`
|
|
19
|
+
: ` Application ${controller.name} manages this one from Git and can turn it back on when it syncs; to keep it off, change it in Git.`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function argoResumeDescription(suspended: boolean, settings: ArgoActionHandlers['resumeSettings']): string {
|
|
23
|
+
if (suspended) return 'Turn auto-sync back on with its current or saved settings.'
|
|
24
|
+
if (!settings) return 'Turn on automated sync, so Argo applies new commits from Git.'
|
|
25
|
+
return `Argo applies new commits${settings.selfHeal ? ' and reverts live edits (self-heal)' : ''}. Resources removed from Git are ${settings.prune ? 'deleted (prune on)' : 'kept (prune off)'}.`
|
|
26
|
+
}
|
|
11
27
|
|
|
12
28
|
// ---------------------------------------------------------------------------
|
|
13
29
|
// Props
|
|
@@ -76,11 +92,22 @@ export interface ArgoActionHandlers {
|
|
|
76
92
|
terminating: boolean
|
|
77
93
|
suspending: boolean
|
|
78
94
|
resuming: boolean
|
|
79
|
-
// autoSyncEnabled: caller derives
|
|
95
|
+
// autoSyncEnabled: caller derives via isArgoAutoSyncEnabled.
|
|
80
96
|
autoSyncEnabled: boolean
|
|
81
97
|
// isRunning: caller derives from status.operationState.phase === 'Running'.
|
|
82
98
|
isRunning: boolean
|
|
83
99
|
operationInProgress?: boolean
|
|
100
|
+
// Set when the connected Radar is too old to turn this app's auto-sync back
|
|
101
|
+
// on (isArgoAutoSyncSwitchedOff): Enable auto-sync renders disabled, with
|
|
102
|
+
// the upgrade note under the header.
|
|
103
|
+
resumeUpgrade?: RadarUpgradeRequirement | null
|
|
104
|
+
// What Enable auto-sync turns on when Radar didn't suspend the app
|
|
105
|
+
// (argoResumeSettings). Without it the tooltip makes no prune claim.
|
|
106
|
+
resumeSettings?: { prune: boolean; selfHeal: boolean } | null
|
|
107
|
+
resumeBlockedReason?: string
|
|
108
|
+
// The Argo CD object that can turn auto-sync back on after Disable
|
|
109
|
+
// (argoAutoSyncController); its tooltip names it.
|
|
110
|
+
autoSyncController?: { kind: 'ApplicationSet' | 'Application'; name: string } | null
|
|
84
111
|
}
|
|
85
112
|
|
|
86
113
|
export interface FluxActionHandlers {
|
|
@@ -125,6 +152,8 @@ export interface GitOpsDetailLayoutProps {
|
|
|
125
152
|
// Insights — drives StatusStrip + IssuesBand
|
|
126
153
|
insight: GitOpsInsight | null
|
|
127
154
|
insightLoading: boolean
|
|
155
|
+
// Tree warnings are independent of the insights request's success.
|
|
156
|
+
warnings?: string[]
|
|
128
157
|
// Issue interactions — caller wires onSelectIssue to jump to Changes tab
|
|
129
158
|
// and highlight the affected resource.
|
|
130
159
|
onSelectIssue?: (issue: GitOpsIssue) => void
|
|
@@ -229,6 +258,7 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
229
258
|
detail,
|
|
230
259
|
insight,
|
|
231
260
|
insightLoading,
|
|
261
|
+
warnings,
|
|
232
262
|
onSelectIssue,
|
|
233
263
|
onRemediate,
|
|
234
264
|
remediationPending,
|
|
@@ -280,6 +310,9 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
280
310
|
|
|
281
311
|
const effectiveSuspended = status?.suspended ?? false
|
|
282
312
|
const argoOperationInProgress = argo?.operationInProgress ?? argo?.isRunning ?? false
|
|
313
|
+
const argoSyncDisabled = effectiveSuspended || terminating || argoOperationInProgress
|
|
314
|
+
const resumeUpgrade = isArgoApp && argo && !argo.autoSyncEnabled ? argo.resumeUpgrade ?? null : null
|
|
315
|
+
const resumeBlockedReason = isArgoApp && argo && !argo.autoSyncEnabled ? argo.resumeBlockedReason : undefined
|
|
283
316
|
// graphFullscreen hides everything chrome-side; the body region expands.
|
|
284
317
|
// Mirrors the OSS GitOpsDetailView shell behavior so the visual chrome
|
|
285
318
|
// class set carries through.
|
|
@@ -365,6 +398,8 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
365
398
|
{detail.autoSyncMode && <AppFact label="Sync mode" value={detail.autoSyncMode} />}
|
|
366
399
|
</div>
|
|
367
400
|
)}
|
|
401
|
+
{resumeBlockedReason && <p className="mt-2 text-xs text-theme-text-secondary">{resumeBlockedReason}</p>}
|
|
402
|
+
{resumeUpgrade && <RadarUpgradeNote requirement={resumeUpgrade} className="mt-2" />}
|
|
368
403
|
</div>
|
|
369
404
|
{/* Action buttons */}
|
|
370
405
|
<div className="flex flex-wrap items-center gap-2">
|
|
@@ -376,7 +411,7 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
376
411
|
icon={ArrowDownUp}
|
|
377
412
|
loading={argo.syncing}
|
|
378
413
|
onClick={argo.onSyncRequested}
|
|
379
|
-
disabled={
|
|
414
|
+
disabled={argoSyncDisabled}
|
|
380
415
|
disabledReason={terminating
|
|
381
416
|
? terminatingActionTooltip
|
|
382
417
|
: argoOperationInProgress
|
|
@@ -411,7 +446,7 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
411
446
|
{argo.autoSyncEnabled ? (
|
|
412
447
|
<ActionButton
|
|
413
448
|
label="Disable auto-sync"
|
|
414
|
-
description=
|
|
449
|
+
description={`Stop Argo from automatically syncing Git changes.${argoSyncDisabled ? '' : ' Manual Sync still works.'}${argoAutoSyncControllerNote(argo.autoSyncController)}`}
|
|
415
450
|
icon={Pause}
|
|
416
451
|
loading={argo.suspending}
|
|
417
452
|
onClick={argo.onSuspend}
|
|
@@ -421,12 +456,16 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
421
456
|
) : (
|
|
422
457
|
<ActionButton
|
|
423
458
|
label="Enable auto-sync"
|
|
424
|
-
description=
|
|
459
|
+
description={argoResumeDescription(effectiveSuspended, argo.resumeSettings)}
|
|
425
460
|
icon={Play}
|
|
426
461
|
loading={argo.resuming}
|
|
427
|
-
onClick={argo.onResume}
|
|
428
|
-
disabled={terminating}
|
|
429
|
-
disabledReason={terminating
|
|
462
|
+
onClick={() => !resumeBlockedReason && !resumeUpgrade && argo.onResume()}
|
|
463
|
+
disabled={terminating || !!resumeUpgrade || !!resumeBlockedReason}
|
|
464
|
+
disabledReason={terminating
|
|
465
|
+
? terminatingActionTooltip
|
|
466
|
+
: resumeBlockedReason || (resumeUpgrade
|
|
467
|
+
? radarUpgradeNoteText(resumeUpgrade)
|
|
468
|
+
: undefined)}
|
|
430
469
|
/>
|
|
431
470
|
)}
|
|
432
471
|
</>
|
|
@@ -528,6 +567,14 @@ export function GitOpsDetailLayout(props: GitOpsDetailLayoutProps) {
|
|
|
528
567
|
<div className="p-4 text-sm text-red-500">Failed to load GitOps view: {resourceError.message}</div>
|
|
529
568
|
) : (
|
|
530
569
|
<div className={bodyShellClass}>
|
|
570
|
+
{((warnings?.length ?? 0) > 0 || (insight?.warnings?.length ?? 0) > 0) && (
|
|
571
|
+
<AlertBanner
|
|
572
|
+
variant="warning"
|
|
573
|
+
title="Limited resource view"
|
|
574
|
+
items={[...new Set([...(warnings ?? []), ...(insight?.warnings ?? [])])]}
|
|
575
|
+
className="shrink-0 rounded-none border-x-0 border-t-0"
|
|
576
|
+
/>
|
|
577
|
+
)}
|
|
531
578
|
<div className="flex shrink-0 items-center justify-between gap-3 border-b border-theme-border bg-theme-base px-4 py-2">
|
|
532
579
|
<div className="flex items-center gap-1 rounded-md border border-theme-border bg-theme-surface p-1">
|
|
533
580
|
<ViewButton active={activeTab === 'topology'} icon={GitBranch} label="Topology" onClick={() => onTabChange('topology')} />
|
|
@@ -38,7 +38,7 @@ export const APP_TREE_API_ERROR_FINDINGS = 'Meanwhile, rows marked "Found by Rad
|
|
|
38
38
|
export const APP_TREE_API_ERROR_NO_FINDINGS = "Radar didn't find a problem on its own."
|
|
39
39
|
|
|
40
40
|
export const REMOTE_DESTINATION_NOTICE =
|
|
41
|
-
"
|
|
41
|
+
"The recorded resources aren't confirmed to belong to this cluster, so local health and generated resources aren't shown."
|
|
42
42
|
export const REMOTE_FLUX_TARGET_NOTICE =
|
|
43
43
|
"This applies its resources to a different cluster, so they aren't visible from here."
|
|
44
44
|
|
|
@@ -34,9 +34,11 @@ import { FacetSection, FacetButton } from '../ui/Facet'
|
|
|
34
34
|
import { SortableTh, TH_CLASS, type SortDir } from '../ui/SortableTh'
|
|
35
35
|
import { DistributionBar } from '../ui/DistributionBar'
|
|
36
36
|
import { RowActionMenu, type RowActionItem } from '../ui/RowActionMenu'
|
|
37
|
+
import { radarUpgradeNoteText } from '../ui/RadarUpgradeNote'
|
|
38
|
+
import type { RadarUpgradeRequirement } from '../../types/fetch-error'
|
|
37
39
|
import { BoardTableSkeleton, BoardRailSkeleton } from '../ui/BoardSkeleton'
|
|
38
40
|
import { getGitOpsResourceStatus } from './detail-helpers'
|
|
39
|
-
import { isArgoOperationInProgress, isArgoSuspendedByRadar } from '../resources/resource-utils-argo'
|
|
41
|
+
import { isArgoAutoSyncEnabled, isArgoAutoSyncSwitchedOff, isArgoOperationInProgress, isArgoSuspendedByRadar } from '../resources/resource-utils-argo'
|
|
40
42
|
import { toggleSet } from './GitOpsGraphFilterRail'
|
|
41
43
|
import { useFilterState, defineFilterSchema } from '../../filter-state'
|
|
42
44
|
import { parseContextName } from '../../utils/context-name'
|
|
@@ -288,6 +290,10 @@ export interface GitOpsTableViewProps {
|
|
|
288
290
|
// In-flight action state, keyed by `row.id`. Drives the per-item
|
|
289
291
|
// spinner so the user can tell which Sync/Refresh is still running.
|
|
290
292
|
pendingRowActions?: Map<string, Set<GitOpsRowAction>>
|
|
293
|
+
// Set when the connected Radar is too old to turn auto-sync back on for an
|
|
294
|
+
// Application with automated.enabled: false; its Resume renders disabled.
|
|
295
|
+
argoResumeUpgrade?: RadarUpgradeRequirement | null
|
|
296
|
+
argoResumeBlockedReason?: string
|
|
291
297
|
}
|
|
292
298
|
|
|
293
299
|
// ----- Main component --------------------------------------------------------
|
|
@@ -330,6 +336,8 @@ export function GitOpsTableView({
|
|
|
330
336
|
onClearNamespaces,
|
|
331
337
|
onRowAction,
|
|
332
338
|
pendingRowActions,
|
|
339
|
+
argoResumeUpgrade,
|
|
340
|
+
argoResumeBlockedReason,
|
|
333
341
|
filtersSide = 'left',
|
|
334
342
|
}: GitOpsTableViewProps) {
|
|
335
343
|
const searchInputRef = useRef<HTMLInputElement>(null)
|
|
@@ -858,6 +866,8 @@ export function GitOpsTableView({
|
|
|
858
866
|
destinationHrefFor={destinationHrefFor}
|
|
859
867
|
onRowAction={onRowAction}
|
|
860
868
|
pendingRowActions={pendingRowActions}
|
|
869
|
+
argoResumeUpgrade={argoResumeUpgrade}
|
|
870
|
+
argoResumeBlockedReason={argoResumeBlockedReason}
|
|
861
871
|
/>
|
|
862
872
|
)}
|
|
863
873
|
</div>
|
|
@@ -1292,6 +1302,8 @@ function GitOpsTable({
|
|
|
1292
1302
|
destinationHrefFor,
|
|
1293
1303
|
onRowAction,
|
|
1294
1304
|
pendingRowActions,
|
|
1305
|
+
argoResumeUpgrade,
|
|
1306
|
+
argoResumeBlockedReason,
|
|
1295
1307
|
}: {
|
|
1296
1308
|
rows: GitOpsRow[]
|
|
1297
1309
|
sort: { key: SortKey; dir: SortDir } | null
|
|
@@ -1303,6 +1315,8 @@ function GitOpsTable({
|
|
|
1303
1315
|
destinationHrefFor?: (row: GitOpsRow, destination: FleetDestinationStamp) => string
|
|
1304
1316
|
onRowAction?: (row: GitOpsRow, action: GitOpsRowAction) => void
|
|
1305
1317
|
pendingRowActions?: Map<string, Set<GitOpsRowAction>>
|
|
1318
|
+
argoResumeUpgrade?: RadarUpgradeRequirement | null
|
|
1319
|
+
argoResumeBlockedReason?: string
|
|
1306
1320
|
}) {
|
|
1307
1321
|
const showActions = !!onRowAction
|
|
1308
1322
|
return (
|
|
@@ -1409,7 +1423,7 @@ function GitOpsTable({
|
|
|
1409
1423
|
className="overflow-visible border-b border-theme-border px-2 py-2 text-right align-middle"
|
|
1410
1424
|
onClick={(e) => e.stopPropagation()}
|
|
1411
1425
|
>
|
|
1412
|
-
<RowActionMenu items={buildRowActionItems(row, onRowAction, pendingRowActions)} />
|
|
1426
|
+
<RowActionMenu items={buildRowActionItems(row, onRowAction, pendingRowActions, argoResumeUpgrade, argoResumeBlockedReason)} />
|
|
1413
1427
|
</td>
|
|
1414
1428
|
)}
|
|
1415
1429
|
</tr>
|
|
@@ -1424,10 +1438,12 @@ function GitOpsTable({
|
|
|
1424
1438
|
// the row's tool (Argo vs Flux), current suspend state, terminating state,
|
|
1425
1439
|
// and Argo's operationState.phase (used to gate the Terminate entry — only
|
|
1426
1440
|
// shown while a sync is mid-flight, mirroring the detail-page condition).
|
|
1427
|
-
function buildRowActionItems(
|
|
1441
|
+
export function buildRowActionItems(
|
|
1428
1442
|
row: GitOpsRow,
|
|
1429
1443
|
onAction: (row: GitOpsRow, action: GitOpsRowAction) => void,
|
|
1430
1444
|
pending?: Map<string, Set<GitOpsRowAction>>,
|
|
1445
|
+
argoResumeUpgrade?: RadarUpgradeRequirement | null,
|
|
1446
|
+
argoResumeBlockedReason?: string,
|
|
1431
1447
|
): RowActionItem[] {
|
|
1432
1448
|
const inFlight = pending?.get(row.id)
|
|
1433
1449
|
const isPending = (action: GitOpsRowAction) => inFlight?.has(action) ?? false
|
|
@@ -1480,13 +1496,16 @@ function buildRowActionItems(
|
|
|
1480
1496
|
pending: isPending('hard-refresh'),
|
|
1481
1497
|
})
|
|
1482
1498
|
if (suspended) {
|
|
1499
|
+
const resumeBlockedReason = isArgoAutoSyncSwitchedOff(row.raw)
|
|
1500
|
+
? argoResumeBlockedReason || (argoResumeUpgrade ? radarUpgradeNoteText(argoResumeUpgrade) : undefined)
|
|
1501
|
+
: undefined
|
|
1483
1502
|
items.push({
|
|
1484
1503
|
key: 'resume',
|
|
1485
1504
|
label: 'Resume',
|
|
1486
1505
|
icon: Play,
|
|
1487
|
-
onClick: () => onAction(row, 'resume'),
|
|
1488
|
-
disabled: terminating,
|
|
1489
|
-
disabledReason: terminating ? terminatingReason :
|
|
1506
|
+
onClick: () => !resumeBlockedReason && onAction(row, 'resume'),
|
|
1507
|
+
disabled: terminating || !!resumeBlockedReason,
|
|
1508
|
+
disabledReason: terminating ? terminatingReason : resumeBlockedReason,
|
|
1490
1509
|
pending: isPending('resume'),
|
|
1491
1510
|
divider: true,
|
|
1492
1511
|
})
|
|
@@ -2007,7 +2026,7 @@ export function normalizeArgoApplication(resource: any): GitOpsRow {
|
|
|
2007
2026
|
destinationNamespace: resource.spec?.destination?.namespace ?? '',
|
|
2008
2027
|
createdAt: resource.metadata?.creationTimestamp ?? '',
|
|
2009
2028
|
lastSync: argoLastSync ?? '',
|
|
2010
|
-
autoSync:
|
|
2029
|
+
autoSync: isArgoAutoSyncEnabled(resource),
|
|
2011
2030
|
terminating: isTerminating(resource),
|
|
2012
2031
|
terminationStartedAt: terminationStartedAt(resource),
|
|
2013
2032
|
raw: resource,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act } from 'react'
|
|
3
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { RollbackDialog } from './RollbackDialog'
|
|
6
|
+
|
|
7
|
+
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true)
|
|
8
|
+
|
|
9
|
+
let root: Root | undefined
|
|
10
|
+
|
|
11
|
+
afterEach(async () => {
|
|
12
|
+
if (root) await act(async () => root?.unmount())
|
|
13
|
+
root = undefined
|
|
14
|
+
document.body.replaceChildren()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('RollbackDialog confirmation', () => {
|
|
18
|
+
it('explains rollback risk and keeps dry-run copy distinct', async () => {
|
|
19
|
+
const host = document.createElement('div')
|
|
20
|
+
document.body.appendChild(host)
|
|
21
|
+
root = createRoot(host)
|
|
22
|
+
const onConfirm = vi.fn()
|
|
23
|
+
await act(async () => root?.render(
|
|
24
|
+
<RollbackDialog open appLabel="argocd/demo" revision="abc123" historyId="0" onCancel={() => {}} onConfirm={onConfirm} />,
|
|
25
|
+
))
|
|
26
|
+
const dialog = document.querySelector<HTMLElement>('[role="dialog"]')!
|
|
27
|
+
expect(dialog.textContent).toContain('Auto-sync is off. Later changes can still cause drift. Syncing or enabling auto-sync deploys the current revision and can undo the rollback.')
|
|
28
|
+
expect(dialog.textContent).toContain("Delete tracked resources that aren't in the rollback target")
|
|
29
|
+
expect(dialog.textContent).toContain("Off by default. The target's resources are still applied; other tracked resources are kept.")
|
|
30
|
+
expect(dialog.textContent).toContain("Uses the Application's current sync options. Sync hooks don't run.")
|
|
31
|
+
const dryRun = dialog.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')[1]
|
|
32
|
+
await act(async () => dryRun.click())
|
|
33
|
+
expect(dialog.textContent).toContain('Argo will preview syncing this application to a previous revision without applying changes.')
|
|
34
|
+
expect(dialog.textContent).not.toContain('Later changes can still cause drift.')
|
|
35
|
+
const confirm = [...dialog.querySelectorAll<HTMLButtonElement>('button')].find(button => button.textContent === 'Run dry-run')!
|
|
36
|
+
await act(async () => confirm.click())
|
|
37
|
+
expect(onConfirm).toHaveBeenCalledWith({ prune: false, dryRun: true })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it.each(['https://example.com/repo/very/long/path/to/guestbook', 'guestbook (+2 more)'])('shows the selected source with wrapping (%s)', async (source) => {
|
|
41
|
+
const host = document.createElement('div')
|
|
42
|
+
document.body.appendChild(host)
|
|
43
|
+
root = createRoot(host)
|
|
44
|
+
await act(async () => root?.render(
|
|
45
|
+
<RollbackDialog open appLabel="argocd/demo" revision="abc123" historyId="0" source={source} onCancel={() => {}} onConfirm={() => {}} />,
|
|
46
|
+
))
|
|
47
|
+
const dialog = document.querySelector<HTMLElement>('[role="dialog"]')!
|
|
48
|
+
const label = [...dialog.querySelectorAll('dt')].find(dt => dt.textContent === 'Source')
|
|
49
|
+
expect(label).toBeDefined()
|
|
50
|
+
expect(label?.nextElementSibling?.textContent).toBe(source)
|
|
51
|
+
expect(label?.nextElementSibling?.classList.contains('break-words')).toBe(true)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it.each([true, false])('shows controller compatibility when either the current Application or entry uses multiple sources (%s)', async (multiSourceWarning) => {
|
|
55
|
+
const host = document.createElement('div')
|
|
56
|
+
document.body.appendChild(host)
|
|
57
|
+
root = createRoot(host)
|
|
58
|
+
await act(async () => root?.render(
|
|
59
|
+
<RollbackDialog open appLabel="argocd/demo" revision="aaa111, bbb222" multiSourceWarning={multiSourceWarning} onCancel={() => {}} onConfirm={() => {}} />,
|
|
60
|
+
))
|
|
61
|
+
const dialog = document.querySelector<HTMLElement>('[role="dialog"]')!
|
|
62
|
+
const note = 'On Argo CD before 2.12, rolling back across a single-source/multi-source change or to a multi-source entry can deploy the wrong revisions or render nothing; with prune, it can delete resources.'
|
|
63
|
+
expect(dialog.textContent?.includes(note)).toBe(multiSourceWarning)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('disables confirmation while support is being checked', async () => {
|
|
69
|
+
const host = document.createElement('div')
|
|
70
|
+
document.body.appendChild(host)
|
|
71
|
+
root = createRoot(host)
|
|
72
|
+
const confirm = vi.fn()
|
|
73
|
+
await act(async () => root?.render(<RollbackDialog open appLabel="argocd/demo" revision="old" blockedReason="Checking this cluster's Radar…" onCancel={() => {}} onConfirm={confirm} />))
|
|
74
|
+
const dialog = document.querySelector<HTMLElement>('[role="dialog"]')!
|
|
75
|
+
expect(dialog.textContent).toContain("Checking this cluster's Radar…")
|
|
76
|
+
const button = [...dialog.querySelectorAll<HTMLButtonElement>('button')].find(b => b.textContent === 'Roll back')!
|
|
77
|
+
expect(button.disabled).toBe(true)
|
|
78
|
+
await act(async () => button.click())
|
|
79
|
+
expect(confirm).not.toHaveBeenCalled()
|
|
80
|
+
})
|
|
@@ -24,12 +24,16 @@ export interface RollbackDialogProps {
|
|
|
24
24
|
appLabel: string
|
|
25
25
|
revision: string
|
|
26
26
|
historyId?: string
|
|
27
|
+
source?: string
|
|
28
|
+
// Either the current Application or the selected entry uses plural sources.
|
|
29
|
+
multiSourceWarning?: boolean
|
|
27
30
|
pending?: boolean
|
|
31
|
+
blockedReason?: string
|
|
28
32
|
onCancel: () => void
|
|
29
33
|
onConfirm: (opts: { prune: boolean; dryRun: boolean }) => void
|
|
30
34
|
}
|
|
31
35
|
|
|
32
|
-
export function RollbackDialog({ open, appLabel, revision, historyId, pending, onCancel, onConfirm }: RollbackDialogProps) {
|
|
36
|
+
export function RollbackDialog({ open, appLabel, revision, historyId, source, multiSourceWarning, pending, blockedReason, onCancel, onConfirm }: RollbackDialogProps) {
|
|
33
37
|
const titleId = useId()
|
|
34
38
|
const [prune, setPrune] = useState(false)
|
|
35
39
|
const [dryRun, setDryRun] = useState(false)
|
|
@@ -49,8 +53,16 @@ export function RollbackDialog({ open, appLabel, revision, historyId, pending, o
|
|
|
49
53
|
</div>
|
|
50
54
|
<div className="space-y-4 px-4 py-4 text-sm">
|
|
51
55
|
<div className="rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-700 dark:text-amber-400">
|
|
52
|
-
|
|
56
|
+
{dryRun
|
|
57
|
+
? 'Argo will preview syncing this application to a previous revision without applying changes.'
|
|
58
|
+
: 'Argo will sync this application to a previous revision. Auto-sync is off. Later changes can still cause drift. Syncing or enabling auto-sync deploys the current revision and can undo the rollback.'}
|
|
53
59
|
</div>
|
|
60
|
+
{multiSourceWarning && (
|
|
61
|
+
<p className="text-xs text-warning-text">
|
|
62
|
+
On Argo CD before 2.12, rolling back across a single-source/multi-source change or to a multi-source entry can deploy the wrong revisions or render nothing; with prune, it can delete resources.
|
|
63
|
+
</p>
|
|
64
|
+
)}
|
|
65
|
+
{blockedReason && <p className="text-xs text-theme-text-secondary">{blockedReason}</p>}
|
|
54
66
|
<dl className="grid grid-cols-[80px_minmax(0,1fr)] gap-x-3 gap-y-1 text-xs">
|
|
55
67
|
<dt className="text-theme-text-tertiary">Revision</dt>
|
|
56
68
|
<dd className="min-w-0">
|
|
@@ -58,6 +70,12 @@ export function RollbackDialog({ open, appLabel, revision, historyId, pending, o
|
|
|
58
70
|
<span className="block truncate font-mono text-theme-text-primary">{revision || '-'}</span>
|
|
59
71
|
</Tooltip>
|
|
60
72
|
</dd>
|
|
73
|
+
{source && (
|
|
74
|
+
<>
|
|
75
|
+
<dt className="text-theme-text-tertiary">Source</dt>
|
|
76
|
+
<dd className="min-w-0 break-words text-theme-text-primary">{source}</dd>
|
|
77
|
+
</>
|
|
78
|
+
)}
|
|
61
79
|
{historyId && (
|
|
62
80
|
<>
|
|
63
81
|
<dt className="text-theme-text-tertiary">History ID</dt>
|
|
@@ -66,6 +84,7 @@ export function RollbackDialog({ open, appLabel, revision, historyId, pending, o
|
|
|
66
84
|
)}
|
|
67
85
|
</dl>
|
|
68
86
|
<div className="space-y-2">
|
|
87
|
+
<p className="text-xs text-theme-text-tertiary">Uses the Application's current sync options. Sync hooks don't run.</p>
|
|
69
88
|
<label className="flex items-start gap-2">
|
|
70
89
|
<input
|
|
71
90
|
type="checkbox"
|
|
@@ -75,8 +94,8 @@ export function RollbackDialog({ open, appLabel, revision, historyId, pending, o
|
|
|
75
94
|
className="mt-0.5 h-3.5 w-3.5 cursor-pointer accent-sky-500 disabled:cursor-not-allowed"
|
|
76
95
|
/>
|
|
77
96
|
<div className="min-w-0">
|
|
78
|
-
<div className="text-xs text-theme-text-primary">
|
|
79
|
-
<div className="text-[11px] text-theme-text-tertiary">Off by default
|
|
97
|
+
<div className="text-xs text-theme-text-primary">Delete tracked resources that aren't in the rollback target</div>
|
|
98
|
+
<div className="text-[11px] text-theme-text-tertiary">Off by default. The target's resources are still applied; other tracked resources are kept.</div>
|
|
80
99
|
</div>
|
|
81
100
|
</label>
|
|
82
101
|
<label className="flex items-start gap-2">
|
|
@@ -105,8 +124,8 @@ export function RollbackDialog({ open, appLabel, revision, historyId, pending, o
|
|
|
105
124
|
</button>
|
|
106
125
|
<button
|
|
107
126
|
type="button"
|
|
108
|
-
onClick={() => onConfirm({ prune, dryRun })}
|
|
109
|
-
disabled={pending}
|
|
127
|
+
onClick={() => !blockedReason && onConfirm({ prune, dryRun })}
|
|
128
|
+
disabled={pending || !!blockedReason}
|
|
110
129
|
className="inline-flex items-center gap-1.5 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-1.5 text-xs font-medium text-amber-700 hover:bg-amber-500/20 disabled:cursor-not-allowed disabled:opacity-50 dark:text-amber-400"
|
|
111
130
|
>
|
|
112
131
|
{pending ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <History className="h-3.5 w-3.5" />}
|