github.com/argoproj/argo-cd/v3@v3.2.1/ui/src/app/applications/components/application-create-panel/application-create-panel.tsx (about)

     1  /* eslint-disable no-prototype-builtins */
     2  import {AutocompleteField, Checkbox, DataLoader, DropDownMenu, FormField, HelpIcon, Select} from 'argo-ui';
     3  import * as deepMerge from 'deepmerge';
     4  import * as React from 'react';
     5  import {FieldApi, Form, FormApi, FormField as ReactFormField, Text} from 'react-form';
     6  import {RevisionHelpIcon, YamlEditor} from '../../../shared/components';
     7  import * as models from '../../../shared/models';
     8  import {services} from '../../../shared/services';
     9  import {ApplicationParameters} from '../application-parameters/application-parameters';
    10  import {ApplicationRetryOptions} from '../application-retry-options/application-retry-options';
    11  import {ApplicationSyncOptionsField} from '../application-sync-options/application-sync-options';
    12  import {RevisionFormField} from '../revision-form-field/revision-form-field';
    13  import {SetFinalizerOnApplication} from './set-finalizer-on-application';
    14  import './application-create-panel.scss';
    15  import {getAppDefaultSource} from '../utils';
    16  import {debounce} from 'lodash-es';
    17  
    18  const jsonMergePatch = require('json-merge-patch');
    19  
    20  const appTypes = new Array<{field: string; type: models.AppSourceType}>(
    21      {type: 'Helm', field: 'helm'},
    22      {type: 'Kustomize', field: 'kustomize'},
    23      {type: 'Directory', field: 'directory'},
    24      {type: 'Plugin', field: 'plugin'}
    25  );
    26  
    27  const DEFAULT_APP: Partial<models.Application> = {
    28      apiVersion: 'argoproj.io/v1alpha1',
    29      kind: 'Application',
    30      metadata: {
    31          name: ''
    32      },
    33      spec: {
    34          destination: {
    35              name: undefined,
    36              namespace: '',
    37              server: undefined
    38          },
    39          source: {
    40              path: '',
    41              repoURL: '',
    42              targetRevision: 'HEAD'
    43          },
    44          sources: [],
    45          project: ''
    46      }
    47  };
    48  
    49  const AutoSyncFormField = ReactFormField((props: {fieldApi: FieldApi; className: string}) => {
    50      const manual = 'Manual';
    51      const auto = 'Automatic';
    52      const {
    53          fieldApi: {getValue, setValue}
    54      } = props;
    55      const automated = getValue() as models.Automated;
    56      return (
    57          <React.Fragment>
    58              <label>Sync Policy</label>
    59              <Select
    60                  value={automated ? auto : manual}
    61                  options={[manual, auto]}
    62                  onChange={opt => {
    63                      setValue(opt.value === auto ? {prune: false, selfHeal: false, enabled: true} : null);
    64                  }}
    65              />
    66              {automated && (
    67                  <div className='application-create-panel__sync-params'>
    68                      <div className='checkbox-container'>
    69                          <Checkbox onChange={val => setValue({...automated, enabled: val})} checked={automated.enabled === undefined ? true : automated.enabled} id='policyEnable' />
    70                          <label htmlFor='policyEnable'>Enable Auto-Sync</label>
    71                          <HelpIcon title='If checked, application will automatically sync when changes are detected' />
    72                      </div>
    73                      <div className='checkbox-container'>
    74                          <Checkbox onChange={val => setValue({...automated, prune: val})} checked={!!automated.prune} id='policyPrune' />
    75                          <label htmlFor='policyPrune'>Prune Resources</label>
    76                          <HelpIcon title='If checked, Argo will delete resources if they are no longer defined in Git' />
    77                      </div>
    78                      <div className='checkbox-container'>
    79                          <Checkbox onChange={val => setValue({...automated, selfHeal: val})} checked={!!automated.selfHeal} id='policySelfHeal' />
    80                          <label htmlFor='policySelfHeal'>Self Heal</label>
    81                          <HelpIcon title='If checked, Argo will force the state defined in Git into the cluster when a deviation in the cluster is detected' />
    82                      </div>
    83                  </div>
    84              )}
    85          </React.Fragment>
    86      );
    87  });
    88  
    89  function normalizeAppSource(app: models.Application, type: string): boolean {
    90      const source = getAppDefaultSource(app);
    91      const repoType = source.repoURL.startsWith('oci://') ? 'oci' : (source.hasOwnProperty('chart') && 'helm') || 'git';
    92      if (repoType !== type) {
    93          if (type === 'git' || type === 'oci') {
    94              source.path = source.chart;
    95              delete source.chart;
    96              source.targetRevision = 'HEAD';
    97          } else {
    98              source.chart = source.path;
    99              delete source.path;
   100              source.targetRevision = '';
   101          }
   102          return true;
   103      }
   104      return false;
   105  }
   106  
   107  export const ApplicationCreatePanel = (props: {
   108      app: models.Application;
   109      onAppChanged: (app: models.Application) => any;
   110      createApp: (app: models.Application) => any;
   111      getFormApi: (api: FormApi) => any;
   112  }) => {
   113      const [yamlMode, setYamlMode] = React.useState(false);
   114      const [explicitPathType, setExplicitPathType] = React.useState<{path: string; type: models.AppSourceType}>(null);
   115      const [retry, setRetry] = React.useState(false);
   116      const app = deepMerge(DEFAULT_APP, props.app || {});
   117      const debouncedOnAppChanged = debounce(props.onAppChanged, 800);
   118      const [destinationFieldChanges, setDestinationFieldChanges] = React.useState({destFormat: 'URL', destFormatChanged: null});
   119      const comboSwitchedFromPanel = React.useRef(false);
   120      const currentRepoType = React.useRef(undefined);
   121      const lastGitOrHelmUrl = React.useRef('');
   122      const lastOciUrl = React.useRef('');
   123      let destinationComboValue = destinationFieldChanges.destFormat;
   124  
   125      React.useEffect(() => {
   126          comboSwitchedFromPanel.current = false;
   127      }, []);
   128  
   129      React.useEffect(() => {
   130          return () => {
   131              debouncedOnAppChanged.cancel();
   132          };
   133      }, [debouncedOnAppChanged]);
   134  
   135      function normalizeTypeFields(formApi: FormApi, type: models.AppSourceType) {
   136          const appToNormalize = formApi.getFormState().values;
   137          for (const item of appTypes) {
   138              if (item.type !== type) {
   139                  delete appToNormalize.spec.source[item.field];
   140              }
   141          }
   142          formApi.setAllValues(appToNormalize);
   143      }
   144  
   145      const currentName = app.spec.destination.name;
   146      const currentServer = app.spec.destination.server;
   147      if (destinationFieldChanges.destFormatChanged !== null) {
   148          if (destinationComboValue == 'NAME') {
   149              if (currentName === undefined && currentServer !== undefined && comboSwitchedFromPanel.current === false) {
   150                  destinationComboValue = 'URL';
   151              } else {
   152                  delete app.spec.destination.server;
   153                  if (currentName === undefined) {
   154                      app.spec.destination.name = '';
   155                  }
   156              }
   157          } else {
   158              if (currentServer === undefined && currentName !== undefined && comboSwitchedFromPanel.current === false) {
   159                  destinationComboValue = 'NAME';
   160              } else {
   161                  delete app.spec.destination.name;
   162                  if (currentServer === undefined) {
   163                      app.spec.destination.server = '';
   164                  }
   165              }
   166          }
   167      } else {
   168          if (currentName === undefined && currentServer === undefined) {
   169              destinationComboValue = destinationFieldChanges.destFormat;
   170              app.spec.destination.server = '';
   171          } else {
   172              if (currentName != undefined) {
   173                  destinationComboValue = 'NAME';
   174              } else {
   175                  destinationComboValue = 'URL';
   176              }
   177          }
   178      }
   179  
   180      const onCreateApp = (data: models.Application) => {
   181          if (destinationComboValue === 'URL') {
   182              delete data.spec.destination.name;
   183          } else {
   184              delete data.spec.destination.server;
   185          }
   186  
   187          props.createApp(data);
   188      };
   189  
   190      return (
   191          <DataLoader
   192              key='creation-deps'
   193              load={() =>
   194                  Promise.all([
   195                      services.projects.list('items.metadata.name').then(projects => projects.map(proj => proj.metadata.name).sort()),
   196                      services.clusters.list().then(clusters => clusters.sort()),
   197                      services.repos.list()
   198                  ]).then(([projects, clusters, reposInfo]) => ({projects, clusters, reposInfo}))
   199              }>
   200              {({projects, clusters, reposInfo}) => {
   201                  const repos = reposInfo.map(info => info.repo).sort();
   202                  const repoInfo = reposInfo.find(info => info.repo === app.spec.source.repoURL);
   203                  if (repoInfo) {
   204                      normalizeAppSource(app, repoInfo.type || currentRepoType.current || 'git');
   205                  }
   206                  return (
   207                      <div className='application-create-panel'>
   208                          {(yamlMode && (
   209                              <YamlEditor
   210                                  minHeight={800}
   211                                  initialEditMode={true}
   212                                  input={app}
   213                                  onCancel={() => setYamlMode(false)}
   214                                  onSave={async patch => {
   215                                      props.onAppChanged(jsonMergePatch.apply(app, JSON.parse(patch)));
   216                                      setYamlMode(false);
   217                                      return true;
   218                                  }}
   219                              />
   220                          )) || (
   221                              <Form
   222                                  validateError={(a: models.Application) => ({
   223                                      'metadata.name': !a.metadata.name && 'Application Name is required',
   224                                      'spec.project': !a.spec.project && 'Project Name is required',
   225                                      'spec.source.repoURL': !a.spec.source.repoURL && 'Repository URL is required',
   226                                      'spec.source.targetRevision': !a.spec.source.targetRevision && a.spec.source.hasOwnProperty('chart') && 'Version is required',
   227                                      'spec.source.path': !a.spec.source.path && !a.spec.source.chart && 'Path is required',
   228                                      'spec.source.chart': !a.spec.source.path && !a.spec.source.chart && 'Chart is required',
   229                                      // Verify cluster URL when there is no cluster name field or the name value is empty
   230                                      'spec.destination.server':
   231                                          !a.spec.destination.server && (!a.spec.destination.hasOwnProperty('name') || a.spec.destination.name === '') && 'Cluster URL is required',
   232                                      // Verify cluster name when there is no cluster URL field or the URL value is empty
   233                                      'spec.destination.name':
   234                                          !a.spec.destination.name && (!a.spec.destination.hasOwnProperty('server') || a.spec.destination.server === '') && 'Cluster name is required'
   235                                  })}
   236                                  defaultValues={app}
   237                                  formDidUpdate={state => debouncedOnAppChanged(state.values as any)}
   238                                  onSubmit={onCreateApp}
   239                                  getApi={props.getFormApi}>
   240                                  {api => {
   241                                      const generalPanel = () => (
   242                                          <div className='white-box'>
   243                                              <p>GENERAL</p>
   244                                              {/*
   245                                                      Need to specify "type='button'" because the default type 'submit'
   246                                                      will activate yaml mode whenever enter is pressed while in the panel.
   247                                                      This causes problems with some entry fields that require enter to be
   248                                                      pressed for the value to be accepted.
   249  
   250                                                      See https://github.com/argoproj/argo-cd/issues/4576
   251                                                  */}
   252                                              {!yamlMode && (
   253                                                  <button
   254                                                      type='button'
   255                                                      className='argo-button argo-button--base application-create-panel__yaml-button'
   256                                                      onClick={() => setYamlMode(true)}>
   257                                                      Edit as YAML
   258                                                  </button>
   259                                              )}
   260                                              <div className='argo-form-row'>
   261                                                  <FormField formApi={api} label='Application Name' qeId='application-create-field-app-name' field='metadata.name' component={Text} />
   262                                              </div>
   263                                              <div className='argo-form-row'>
   264                                                  <FormField
   265                                                      formApi={api}
   266                                                      label='Project Name'
   267                                                      qeId='application-create-field-project'
   268                                                      field='spec.project'
   269                                                      component={AutocompleteField}
   270                                                      componentProps={{
   271                                                          items: projects,
   272                                                          filterSuggestions: true
   273                                                      }}
   274                                                  />
   275                                              </div>
   276                                              <div className='argo-form-row'>
   277                                                  <FormField
   278                                                      formApi={api}
   279                                                      field='spec.syncPolicy.automated'
   280                                                      qeId='application-create-field-sync-policy'
   281                                                      component={AutoSyncFormField}
   282                                                  />
   283                                              </div>
   284                                              <div className='argo-form-row'>
   285                                                  <FormField formApi={api} field='metadata.finalizers' component={SetFinalizerOnApplication} />
   286                                              </div>
   287                                              <div className='argo-form-row'>
   288                                                  <label>Sync Options</label>
   289                                                  <FormField formApi={api} field='spec.syncPolicy.syncOptions' component={ApplicationSyncOptionsField} />
   290                                                  <ApplicationRetryOptions
   291                                                      formApi={api}
   292                                                      field='spec.syncPolicy.retry'
   293                                                      retry={retry || (api.getFormState().values.spec.syncPolicy && api.getFormState().values.spec.syncPolicy.retry)}
   294                                                      setRetry={setRetry}
   295                                                      initValues={api.getFormState().values.spec.syncPolicy ? api.getFormState().values.spec.syncPolicy.retry : null}
   296                                                  />
   297                                              </div>
   298                                          </div>
   299                                      );
   300  
   301                                      const repoType = api.getFormState().values.spec.source.repoURL.startsWith('oci://')
   302                                          ? 'oci'
   303                                          : (api.getFormState().values.spec.source.hasOwnProperty('chart') && 'helm') || 'git';
   304                                      const sourcePanel = () => (
   305                                          <div className='white-box'>
   306                                              <p>SOURCE</p>
   307                                              <div className='row argo-form-row'>
   308                                                  <div className='columns small-10'>
   309                                                      <FormField
   310                                                          formApi={api}
   311                                                          label='Repository URL'
   312                                                          qeId='application-create-field-repository-url'
   313                                                          field='spec.source.repoURL'
   314                                                          component={AutocompleteField}
   315                                                          componentProps={{
   316                                                              items: repos,
   317                                                              filterSuggestions: true
   318                                                          }}
   319                                                      />
   320                                                  </div>
   321                                                  <div className='columns small-2'>
   322                                                      <div style={{paddingTop: '1.5em'}}>
   323                                                          {(repoInfo && (
   324                                                              <React.Fragment>
   325                                                                  <span>{(repoInfo.type || 'git').toUpperCase()}</span> <i className='fa fa-check' />
   326                                                              </React.Fragment>
   327                                                          )) || (
   328                                                              <DropDownMenu
   329                                                                  anchor={() => (
   330                                                                      <p>
   331                                                                          {repoType.toUpperCase()} <i className='fa fa-caret-down' />
   332                                                                      </p>
   333                                                                  )}
   334                                                                  qeId='application-create-dropdown-source-repository'
   335                                                                  items={['git', 'helm', 'oci'].map((type: 'git' | 'helm' | 'oci') => ({
   336                                                                      title: type.toUpperCase(),
   337                                                                      action: () => {
   338                                                                          if (repoType !== type) {
   339                                                                              const updatedApp = api.getFormState().values as models.Application;
   340                                                                              const source = getAppDefaultSource(updatedApp);
   341                                                                              // Save the previous URL value for later use
   342                                                                              if (repoType === 'git' || repoType === 'helm') {
   343                                                                                  lastGitOrHelmUrl.current = source.repoURL;
   344                                                                              } else {
   345                                                                                  lastOciUrl.current = source.repoURL;
   346                                                                              }
   347                                                                              currentRepoType.current = type;
   348                                                                              switch (type) {
   349                                                                                  case 'git':
   350                                                                                  case 'oci':
   351                                                                                      if (source.hasOwnProperty('chart')) {
   352                                                                                          source.path = source.chart;
   353                                                                                          delete source.chart;
   354                                                                                      }
   355                                                                                      source.targetRevision = 'HEAD';
   356                                                                                      source.repoURL =
   357                                                                                          type === 'git'
   358                                                                                              ? lastGitOrHelmUrl.current
   359                                                                                              : lastOciUrl.current === ''
   360                                                                                                ? 'oci://'
   361                                                                                                : lastOciUrl.current;
   362                                                                                      break;
   363                                                                                  case 'helm':
   364                                                                                      if (source.hasOwnProperty('path')) {
   365                                                                                          source.chart = source.path;
   366                                                                                          delete source.path;
   367                                                                                      }
   368                                                                                      source.targetRevision = '';
   369                                                                                      source.repoURL = lastGitOrHelmUrl.current;
   370                                                                                      break;
   371                                                                              }
   372                                                                              api.setAllValues(updatedApp);
   373                                                                          }
   374                                                                      }
   375                                                                  }))}
   376                                                              />
   377                                                          )}
   378                                                      </div>
   379                                                  </div>
   380                                              </div>
   381                                              {(repoType === 'oci' && (
   382                                                  <React.Fragment>
   383                                                      <RevisionFormField formApi={api} helpIconTop={'2.5em'} repoURL={app.spec.source.repoURL} repoType={repoType} />
   384                                                      <div className='argo-form-row'>
   385                                                          <DataLoader
   386                                                              input={{repoURL: app.spec.source.repoURL, revision: app.spec.source.targetRevision}}
   387                                                              load={async src =>
   388                                                                  src.repoURL &&
   389                                                                  // TODO: for autocomplete we need to fetch paths that are used by other apps within the same project making use of the same OCI repo
   390                                                                  new Array<string>()
   391                                                              }>
   392                                                              {(paths: string[]) => (
   393                                                                  <FormField
   394                                                                      formApi={api}
   395                                                                      label='Path'
   396                                                                      qeId='application-create-field-path'
   397                                                                      field='spec.source.path'
   398                                                                      component={AutocompleteField}
   399                                                                      componentProps={{
   400                                                                          items: paths,
   401                                                                          filterSuggestions: true
   402                                                                      }}
   403                                                                  />
   404                                                              )}
   405                                                          </DataLoader>
   406                                                      </div>
   407                                                  </React.Fragment>
   408                                              )) ||
   409                                                  (repoType === 'git' && (
   410                                                      <React.Fragment>
   411                                                          <RevisionFormField formApi={api} helpIconTop={'2.5em'} repoURL={app.spec.source.repoURL} repoType={repoType} />
   412                                                          <div className='argo-form-row'>
   413                                                              <DataLoader
   414                                                                  input={{repoURL: app.spec.source.repoURL, revision: app.spec.source.targetRevision}}
   415                                                                  load={async src =>
   416                                                                      (src.repoURL &&
   417                                                                          services.repos
   418                                                                              .apps(src.repoURL, src.revision, app.metadata.name, app.spec.project)
   419                                                                              .then(apps => Array.from(new Set(apps.map(item => item.path))).sort())
   420                                                                              .catch(() => new Array<string>())) ||
   421                                                                      new Array<string>()
   422                                                                  }>
   423                                                                  {(apps: string[]) => (
   424                                                                      <FormField
   425                                                                          formApi={api}
   426                                                                          label='Path'
   427                                                                          qeId='application-create-field-path'
   428                                                                          field='spec.source.path'
   429                                                                          component={AutocompleteField}
   430                                                                          componentProps={{
   431                                                                              items: apps,
   432                                                                              filterSuggestions: true
   433                                                                          }}
   434                                                                      />
   435                                                                  )}
   436                                                              </DataLoader>
   437                                                          </div>
   438                                                      </React.Fragment>
   439                                                  )) || (
   440                                                      <DataLoader
   441                                                          input={{repoURL: app.spec.source.repoURL}}
   442                                                          load={async src =>
   443                                                              (src.repoURL && services.repos.charts(src.repoURL).catch(() => new Array<models.HelmChart>())) ||
   444                                                              new Array<models.HelmChart>()
   445                                                          }>
   446                                                          {(charts: models.HelmChart[]) => {
   447                                                              const selectedChart = charts.find(chart => chart.name === api.getFormState().values.spec.source.chart);
   448                                                              return (
   449                                                                  <div className='row argo-form-row'>
   450                                                                      <div className='columns small-10'>
   451                                                                          <FormField
   452                                                                              formApi={api}
   453                                                                              label='Chart'
   454                                                                              field='spec.source.chart'
   455                                                                              component={AutocompleteField}
   456                                                                              componentProps={{
   457                                                                                  items: charts.map(chart => chart.name),
   458                                                                                  filterSuggestions: true
   459                                                                              }}
   460                                                                          />
   461                                                                      </div>
   462                                                                      <div className='columns small-2'>
   463                                                                          <FormField
   464                                                                              formApi={api}
   465                                                                              field='spec.source.targetRevision'
   466                                                                              component={AutocompleteField}
   467                                                                              componentProps={{
   468                                                                                  items: (selectedChart && selectedChart.versions) || [],
   469                                                                                  filterSuggestions: true
   470                                                                              }}
   471                                                                          />
   472                                                                          <RevisionHelpIcon type='helm' />
   473                                                                      </div>
   474                                                                  </div>
   475                                                              );
   476                                                          }}
   477                                                      </DataLoader>
   478                                                  )}
   479                                          </div>
   480                                      );
   481                                      const destinationPanel = () => (
   482                                          <div className='white-box'>
   483                                              <p>DESTINATION</p>
   484                                              <div className='row argo-form-row'>
   485                                                  {(destinationComboValue.toUpperCase() === 'URL' && (
   486                                                      <div className='columns small-10'>
   487                                                          <FormField
   488                                                              formApi={api}
   489                                                              label='Cluster URL'
   490                                                              qeId='application-create-field-cluster-url'
   491                                                              field='spec.destination.server'
   492                                                              componentProps={{
   493                                                                  items: clusters.map(cluster => cluster.server),
   494                                                                  filterSuggestions: true
   495                                                              }}
   496                                                              component={AutocompleteField}
   497                                                          />
   498                                                      </div>
   499                                                  )) || (
   500                                                      <div className='columns small-10'>
   501                                                          <FormField
   502                                                              formApi={api}
   503                                                              label='Cluster Name'
   504                                                              qeId='application-create-field-cluster-name'
   505                                                              field='spec.destination.name'
   506                                                              componentProps={{
   507                                                                  items: clusters.map(cluster => cluster.name),
   508                                                                  filterSuggestions: true
   509                                                              }}
   510                                                              component={AutocompleteField}
   511                                                          />
   512                                                      </div>
   513                                                  )}
   514                                                  <div className='columns small-2'>
   515                                                      <div style={{paddingTop: '1.5em'}}>
   516                                                          <DropDownMenu
   517                                                              anchor={() => (
   518                                                                  <p>
   519                                                                      {destinationComboValue} <i className='fa fa-caret-down' />
   520                                                                  </p>
   521                                                              )}
   522                                                              qeId='application-create-dropdown-destination'
   523                                                              items={['URL', 'NAME'].map((type: 'URL' | 'NAME') => ({
   524                                                                  title: type,
   525                                                                  action: () => {
   526                                                                      if (destinationComboValue !== type) {
   527                                                                          destinationComboValue = type;
   528                                                                          comboSwitchedFromPanel.current = true;
   529                                                                          setDestinationFieldChanges({destFormat: type, destFormatChanged: 'changed'});
   530                                                                      }
   531                                                                  }
   532                                                              }))}
   533                                                          />
   534                                                      </div>
   535                                                  </div>
   536                                              </div>
   537                                              <div className='argo-form-row'>
   538                                                  <FormField
   539                                                      qeId='application-create-field-namespace'
   540                                                      formApi={api}
   541                                                      label='Namespace'
   542                                                      field='spec.destination.namespace'
   543                                                      component={Text}
   544                                                  />
   545                                              </div>
   546                                          </div>
   547                                      );
   548  
   549                                      const typePanel = () => (
   550                                          <DataLoader
   551                                              input={{
   552                                                  repoURL: app.spec.source.repoURL,
   553                                                  path: app.spec.source.path,
   554                                                  chart: app.spec.source.chart,
   555                                                  targetRevision: app.spec.source.targetRevision,
   556                                                  appName: app.metadata.name
   557                                              }}
   558                                              load={async src => {
   559                                                  if (src.repoURL && src.targetRevision && (src.path || src.chart)) {
   560                                                      return services.repos.appDetails(src, src.appName, app.spec.project, 0, 0).catch(() => ({
   561                                                          type: 'Directory',
   562                                                          details: {}
   563                                                      }));
   564                                                  } else {
   565                                                      return {
   566                                                          type: 'Directory',
   567                                                          details: {}
   568                                                      };
   569                                                  }
   570                                              }}>
   571                                              {(details: models.RepoAppDetails) => {
   572                                                  const type = (explicitPathType && explicitPathType.path === app.spec.source.path && explicitPathType.type) || details.type;
   573                                                  if (details.type !== type) {
   574                                                      switch (type) {
   575                                                          case 'Helm':
   576                                                              details = {
   577                                                                  type,
   578                                                                  path: details.path,
   579                                                                  helm: {name: '', valueFiles: [], path: '', parameters: [], fileParameters: []}
   580                                                              };
   581                                                              break;
   582                                                          case 'Kustomize':
   583                                                              details = {type, path: details.path, kustomize: {path: ''}};
   584                                                              break;
   585                                                          case 'Plugin':
   586                                                              details = {type, path: details.path, plugin: {name: '', env: []}};
   587                                                              break;
   588                                                          // Directory
   589                                                          default:
   590                                                              details = {type, path: details.path, directory: {}};
   591                                                              break;
   592                                                      }
   593                                                  }
   594                                                  return (
   595                                                      <React.Fragment>
   596                                                          <DropDownMenu
   597                                                              anchor={() => (
   598                                                                  <p>
   599                                                                      {type} <i className='fa fa-caret-down' />
   600                                                                  </p>
   601                                                              )}
   602                                                              qeId='application-create-dropdown-source'
   603                                                              items={appTypes.map(item => ({
   604                                                                  title: item.type,
   605                                                                  action: () => {
   606                                                                      setExplicitPathType({type: item.type, path: app.spec.source.path});
   607                                                                      normalizeTypeFields(api, item.type);
   608                                                                  }
   609                                                              }))}
   610                                                          />
   611                                                          <ApplicationParameters
   612                                                              noReadonlyMode={true}
   613                                                              application={app}
   614                                                              details={details}
   615                                                              save={async updatedApp => {
   616                                                                  api.setAllValues(updatedApp);
   617                                                              }}
   618                                                          />
   619                                                      </React.Fragment>
   620                                                  );
   621                                              }}
   622                                          </DataLoader>
   623                                      );
   624  
   625                                      return (
   626                                          <form onSubmit={api.submitForm} role='form' className='width-control'>
   627                                              {generalPanel()}
   628  
   629                                              {sourcePanel()}
   630  
   631                                              {destinationPanel()}
   632  
   633                                              {typePanel()}
   634                                          </form>
   635                                      );
   636                                  }}
   637                              </Form>
   638                          )}
   639                      </div>
   640                  );
   641              }}
   642          </DataLoader>
   643      );
   644  };