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