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  };