mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
feat(ui): improve sync warnings (#25524)
Signed-off-by: Jonathan Winters <wintersjonathan0@gmail.com>
This commit is contained in:
@@ -6,9 +6,36 @@ import * as ReactForm from 'react-form';
|
||||
import './application-sync-options.scss';
|
||||
import {services} from '../../../shared/services';
|
||||
|
||||
export const REPLACE_WARNING = `The resources will be synced using 'kubectl replace/create' command that is a potentially destructive action and might cause resources recreation. For example, it might cause a number of pods to be reset to the minimum number of replicas and cause them to become overloaded.`;
|
||||
const ReplaceWarning = () => (
|
||||
<div>
|
||||
<p>
|
||||
Argo CD will sync using <strong>kubectl replace/create</strong>. This operation <strong>forces resource deletion and recreation</strong>. Proceed only if you understand
|
||||
the risks.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const PruneAllWarning = () => (
|
||||
<div>
|
||||
<p>
|
||||
The resources will be synced using --prune, and all resources are marked to be pruned. Only continue if you want to{' '}
|
||||
<strong>delete all of the Application's resources.</strong>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
const PruneSomeWarning = () => (
|
||||
<div>
|
||||
<p>
|
||||
The resources will be synced using --prune, and some resources are marked to be pruned. Only continue if you want to <strong>delete the pruned resources</strong>.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const REPLACE_WARNING = <ReplaceWarning />;
|
||||
export const FORCE_WARNING = `The resources will be synced using '--force' that is a potentially destructive action and will immediately remove resources from the API and bypasses graceful deletion. Immediate deletion of some resources may result in inconsistency or data loss.`;
|
||||
export const PRUNE_ALL_WARNING = `The resources will be synced using '--prune', and all resources are marked to be pruned. Only continue if you want to delete all of the Application's resources.`;
|
||||
export const PRUNE_ALL_WARNING = <PruneAllWarning />;
|
||||
export const PRUNE_SOME_WARNING = <PruneSomeWarning />;
|
||||
|
||||
export interface ApplicationSyncOptionProps {
|
||||
options: string[];
|
||||
@@ -42,7 +69,7 @@ function selectOption(name: string, label: string, defaultVal: string, values: s
|
||||
);
|
||||
}
|
||||
|
||||
function booleanOption(name: string, label: string, defaultVal: boolean, props: ApplicationSyncOptionProps, invert: boolean, warning: string = null) {
|
||||
function booleanOption(name: string, label: string, defaultVal: boolean, props: ApplicationSyncOptionProps, invert: boolean, warning: string | React.ReactNode = null) {
|
||||
const options = [...(props.options || [])];
|
||||
const prefix = `${name}=`;
|
||||
const index = options.findIndex(item => item.startsWith(prefix));
|
||||
@@ -64,7 +91,7 @@ function booleanOption(name: string, label: string, defaultVal: boolean, props:
|
||||
<label htmlFor={`sync-option-${name}-${props.id}`}>{label}</label>{' '}
|
||||
{warning && (
|
||||
<>
|
||||
<Tooltip content={warning}>
|
||||
<Tooltip content={typeof warning === 'string' ? warning : 'Warning'}>
|
||||
<i className='fa fa-exclamation-triangle' />
|
||||
</Tooltip>
|
||||
{checked && <div className='application-sync-options__warning'>{warning}</div>}
|
||||
@@ -120,7 +147,7 @@ export const ApplicationSyncOptions = (props: ApplicationSyncOptionProps) => (
|
||||
{syncWithReplaceAllowed =>
|
||||
(syncWithReplaceAllowed && (
|
||||
<div className='small-12' style={optionStyle}>
|
||||
{booleanOption('Replace', 'Replace', false, props, false, REPLACE_WARNING)}
|
||||
{booleanOption('Replace', 'Replace', false, props, false)}
|
||||
</div>
|
||||
)) ||
|
||||
null
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
FORCE_WARNING,
|
||||
SyncFlags,
|
||||
REPLACE_WARNING,
|
||||
PRUNE_ALL_WARNING
|
||||
PRUNE_ALL_WARNING,
|
||||
PRUNE_SOME_WARNING
|
||||
} from '../application-sync-options/application-sync-options';
|
||||
import {ComparisonStatusIcon, getAppDefaultSource, nodeKey} from '../utils';
|
||||
|
||||
@@ -68,14 +69,132 @@ export const ApplicationSyncPanel = ({application, selectedResource, hide}: {app
|
||||
const allResourcesAreSelected = selectedResources.length === appResources.length;
|
||||
const syncFlags = {...params.syncFlags} as SyncFlags;
|
||||
|
||||
const allRequirePruning = !selectedResources.some(resource => !resource?.requiresPruning);
|
||||
if (syncFlags.Prune && allRequirePruning && allResourcesAreSelected) {
|
||||
const confirmed = await ctx.popup.confirm('Prune all resources?', () => (
|
||||
<div>
|
||||
<i className='fa fa-exclamation-triangle' style={{color: ARGO_WARNING_COLOR}} />
|
||||
{PRUNE_ALL_WARNING} Are you sure you want to continue?
|
||||
</div>
|
||||
));
|
||||
const resourcesToPrune = selectedResources.filter(resource => resource?.requiresPruning);
|
||||
const allRequirePruning = resourcesToPrune.length === selectedResources.length;
|
||||
const anyRequirePruning = resourcesToPrune.length > 0;
|
||||
const warnAgainstPruneAll = allRequirePruning && allResourcesAreSelected;
|
||||
|
||||
if (syncFlags.Prune) {
|
||||
if (warnAgainstPruneAll) {
|
||||
const confirmed = await ctx.popup.prompt(
|
||||
'Prune all resources?',
|
||||
api => (
|
||||
<div>
|
||||
<p>{PRUNE_ALL_WARNING}</p>
|
||||
<p>
|
||||
<strong>Resources to be deleted ({resourcesToPrune.length}):</strong>
|
||||
</p>
|
||||
<ul style={{maxHeight: '200px', overflowY: 'auto', marginBottom: '1em'}}>
|
||||
{resourcesToPrune.map(resource => (
|
||||
<li key={nodeKey(resource)}>
|
||||
{resource.kind}/{resource.name}
|
||||
{resource.namespace && ` (${resource.namespace})`}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className='argo-form-row'>
|
||||
<FormField
|
||||
label="Please type 'prune' to confirm this action"
|
||||
formApi={api}
|
||||
field='confirmText'
|
||||
qeId='prune-all-field-confirmation'
|
||||
component={Text}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
validate: vals => ({
|
||||
confirmText: vals.confirmText !== 'prune' && "Type 'prune' to confirm"
|
||||
}),
|
||||
submit: async (vals, _, close) => {
|
||||
close();
|
||||
}
|
||||
},
|
||||
{name: 'argo-icon-warning', color: 'warning'},
|
||||
'yellow'
|
||||
);
|
||||
if (!confirmed) {
|
||||
setPending(false);
|
||||
return;
|
||||
}
|
||||
} else if (anyRequirePruning && !warnAgainstPruneAll) {
|
||||
const confirmed = await ctx.popup.prompt(
|
||||
'Prune resources?',
|
||||
api => (
|
||||
<div>
|
||||
<p>{PRUNE_SOME_WARNING}</p>
|
||||
<p>
|
||||
<strong>Resources to be deleted ({resourcesToPrune.length}):</strong>
|
||||
</p>
|
||||
<ul style={{maxHeight: '200px', overflowY: 'auto', marginBottom: '1em'}}>
|
||||
{resourcesToPrune.map(resource => (
|
||||
<li key={nodeKey(resource)}>
|
||||
{resource.kind}/{resource.name}
|
||||
{resource.namespace && ` (${resource.namespace})`}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className='argo-form-row'>
|
||||
<FormField
|
||||
label="Please type 'prune' to confirm this action"
|
||||
formApi={api}
|
||||
field='confirmText'
|
||||
qeId='prune-some-field-confirmation'
|
||||
component={Text}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
validate: vals => ({
|
||||
confirmText: vals.confirmText !== 'prune' && "Type 'prune' to confirm"
|
||||
}),
|
||||
submit: async (vals, _, close) => {
|
||||
close();
|
||||
}
|
||||
},
|
||||
{name: 'argo-icon-warning', color: 'warning'},
|
||||
'yellow'
|
||||
);
|
||||
if (!confirmed) {
|
||||
setPending(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
const replace = params.syncOptions?.findIndex((opt: string) => opt === 'Replace=true') > -1;
|
||||
if (replace) {
|
||||
const confirmed = await ctx.popup.prompt(
|
||||
'Synchronize using replace?',
|
||||
api => (
|
||||
<div>
|
||||
<div>{REPLACE_WARNING}</div>
|
||||
<p>
|
||||
Are you sure you want to <strong>delete and recreate {selectedResources?.length || 0} resources</strong>?
|
||||
</p>
|
||||
<div className='argo-form-row'>
|
||||
<FormField
|
||||
label="Please type 'replace' to confirm this action"
|
||||
formApi={api}
|
||||
field='confirmText'
|
||||
qeId='replace-field-confirmation'
|
||||
component={Text}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
validate: vals => ({
|
||||
confirmText: vals.confirmText !== 'replace' && "Type 'replace' to confirm"
|
||||
}),
|
||||
submit: async (vals, _, close) => {
|
||||
close();
|
||||
}
|
||||
},
|
||||
{name: 'argo-icon-warning', color: 'warning'},
|
||||
'yellow'
|
||||
);
|
||||
if (!confirmed) {
|
||||
setPending(false);
|
||||
return;
|
||||
@@ -84,18 +203,6 @@ export const ApplicationSyncPanel = ({application, selectedResource, hide}: {app
|
||||
if (allResourcesAreSelected) {
|
||||
selectedResources = null;
|
||||
}
|
||||
const replace = params.syncOptions?.findIndex((opt: string) => opt === 'Replace=true') > -1;
|
||||
if (replace) {
|
||||
const confirmed = await ctx.popup.confirm('Synchronize using replace?', () => (
|
||||
<div>
|
||||
<i className='fa fa-exclamation-triangle' style={{color: ARGO_WARNING_COLOR}} /> {REPLACE_WARNING} Are you sure you want to continue?
|
||||
</div>
|
||||
));
|
||||
if (!confirmed) {
|
||||
setPending(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const force = syncFlags.Force || false;
|
||||
|
||||
|
||||
17
ui/yarn.lock
17
ui/yarn.lock
@@ -2442,7 +2442,7 @@ arg@^4.1.0:
|
||||
|
||||
"argo-ui@git+https://github.com/argoproj/argo-ui.git":
|
||||
version "1.0.0"
|
||||
resolved "git+https://github.com/argoproj/argo-ui.git#5cf36101733ce43eed57242a12389f2a7e40bd2b"
|
||||
resolved "git+https://github.com/argoproj/argo-ui.git#6d8ee8b016bf2e1fa81b646cf625f4d0887dd06a"
|
||||
dependencies:
|
||||
"@fortawesome/fontawesome-free" "^6.2.1"
|
||||
"@tippy.js/react" "^3.1.1"
|
||||
@@ -2450,6 +2450,7 @@ arg@^4.1.0:
|
||||
core-js "^3.32.1"
|
||||
foundation-sites "^6.4.3"
|
||||
history "^4.10.1"
|
||||
minimatch "5.1.6"
|
||||
prop-types "^15.8.1"
|
||||
react-autocomplete "1.8.1"
|
||||
react-form "^2.16.0"
|
||||
@@ -6638,6 +6639,13 @@ minimalistic-assert@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimatch@5.1.6, minimatch@^5.0.1:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^3.0.4, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
@@ -6645,13 +6653,6 @@ minimatch@^3.0.4, minimatch@^3.1.2:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^5.0.1:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimatch@^9.0.4:
|
||||
version "9.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51"
|
||||
|
||||
Reference in New Issue
Block a user