argo-cd
159 строк · 8.1 Кб
1import {ErrorNotification, FormField, NotificationType, SlidingPanel} from 'argo-ui';
2import * as React from 'react';
3import {Form, FormApi} from 'react-form';
4import {ARGO_WARNING_COLOR, ProgressPopup, Spinner} from '../../../shared/components';
5import {Consumer, ContextApis} from '../../../shared/context';
6import * as models from '../../../shared/models';
7import {services} from '../../../shared/services';
8import {ApplicationRetryOptions} from '../application-retry-options/application-retry-options';
9import {ApplicationManualSyncFlags, ApplicationSyncOptions, FORCE_WARNING, SyncFlags} from '../application-sync-options/application-sync-options';
10import {ApplicationSelector} from '../../../shared/components';
11import {confirmSyncingAppOfApps, getAppDefaultSource} from '../utils';
12
13interface Progress {
14percentage: number;
15title: string;
16}
17
18export const ApplicationsSyncPanel = ({show, apps, hide}: {show: boolean; apps: models.Application[]; hide: () => void}) => {
19const [form, setForm] = React.useState<FormApi>(null);
20const [progress, setProgress] = React.useState<Progress>(null);
21const getSelectedApps = (params: any) => apps.filter((_, i) => params['app/' + i]);
22const [isPending, setPending] = React.useState(false);
23const syncHandler = (currentForm: FormApi, ctx: ContextApis, applications: models.Application[]) => {
24const formValues = currentForm.getFormState().values;
25const replaceChecked = formValues.syncOptions?.includes('Replace=true');
26const selectedApps = [];
27const selectedAppOfApps: models.Application[] = [];
28let containAppOfApps = false;
29
30for (const key in formValues) {
31if (key.startsWith('app/') && formValues[key]) {
32selectedApps.push(applications[parseInt(key.slice(key.lastIndexOf('/') + 1), 10)]);
33}
34}
35
36selectedApps.forEach(app => {
37if (app.isAppOfAppsPattern) {
38containAppOfApps = true;
39selectedAppOfApps.push(app);
40}
41});
42
43if (replaceChecked && containAppOfApps) {
44confirmSyncingAppOfApps(selectedAppOfApps, ctx, currentForm).then(confirmed => {
45setPending(confirmed ? true : false);
46});
47} else {
48currentForm.submitForm(null);
49}
50};
51return (
52<Consumer>
53{ctx => (
54<SlidingPanel
55isMiddle={true}
56isShown={show}
57onClose={() => hide()}
58header={
59<div>
60<button className='argo-button argo-button--base' disabled={isPending} onClick={() => syncHandler(form, ctx, apps)}>
61<Spinner show={isPending} style={{marginRight: '5px'}} />
62Sync
63</button>{' '}
64<button onClick={() => hide()} className='argo-button argo-button--base-o'>
65Cancel
66</button>
67</div>
68}>
69<Form
70defaultValues={{syncFlags: []}}
71onSubmit={async (params: any) => {
72setPending(true);
73const selectedApps = getSelectedApps(params);
74const syncFlags = {...params.syncFlags} as SyncFlags;
75const force = syncFlags.Force || false;
76if (force) {
77const confirmed = await ctx.popup.confirm('Synchronize with force?', () => (
78<div>
79<i className='fa fa-exclamation-triangle' style={{color: ARGO_WARNING_COLOR}} /> {FORCE_WARNING} Are you sure you want to continue?
80</div>
81));
82if (!confirmed) {
83setPending(false);
84return;
85}
86}
87if (selectedApps.length === 0) {
88ctx.notifications.show({content: `No apps selected`, type: NotificationType.Error});
89setPending(false);
90return;
91}
92
93const syncStrategy: models.SyncStrategy = syncFlags.ApplyOnly || false ? {apply: {force}} : {hook: {force}};
94
95setProgress({percentage: 0, title: 'Starting...'});
96let i = 0;
97for (const app of selectedApps) {
98await services.applications
99.sync(
100app.metadata.name,
101app.metadata.namespace,
102getAppDefaultSource(app).targetRevision,
103syncFlags.Prune || false,
104syncFlags.DryRun || false,
105syncStrategy,
106null,
107params.syncOptions,
108params.retryStrategy
109)
110.catch(e => {
111ctx.notifications.show({
112content: <ErrorNotification title={`Unable to sync ${app.metadata.name}`} e={e} />,
113type: NotificationType.Error
114});
115})
116.finally(() => {
117setPending(false);
118});
119i++;
120setProgress({
121percentage: i / selectedApps.length,
122title: `${i} of ${selectedApps.length} apps now syncing`
123});
124}
125setProgress({percentage: 100, title: 'Complete'});
126}}
127getApi={setForm}>
128{formApi => (
129<React.Fragment>
130<div className='argo-form-row' style={{marginTop: 0}}>
131<h4>Sync app(s)</h4>
132{progress !== null && <ProgressPopup onClose={() => setProgress(null)} percentage={progress.percentage} title={progress.title} />}
133<div style={{marginBottom: '1em'}}>
134<FormField formApi={formApi} field='syncFlags' component={ApplicationManualSyncFlags} />
135</div>
136<div style={{marginBottom: '1em'}}>
137<label>Sync Options</label>
138<ApplicationSyncOptions
139options={formApi.values.syncOptions}
140onChanged={opts => {
141formApi.setTouched('syncOptions', true);
142formApi.setValue('syncOptions', opts);
143}}
144id='applications-sync-panel'
145/>
146</div>
147
148<ApplicationRetryOptions id='applications-sync-panel' formApi={formApi} />
149
150<ApplicationSelector apps={apps} formApi={formApi} />
151</div>
152</React.Fragment>
153)}
154</Form>
155</SlidingPanel>
156)}
157</Consumer>
158);
159};
160