github.com/argoproj/argo-cd/v2@v2.10.9/ui/src/app/settings/components/repos-list/repos-list.tsx (about)

     1  import {AutocompleteField, DropDownMenu, FormField, FormSelect, HelpIcon, NotificationType, SlidingPanel, Tooltip} from 'argo-ui';
     2  import * as PropTypes from 'prop-types';
     3  import * as React from 'react';
     4  import {Form, FormValues, FormApi, Text, TextArea, FormErrors} from 'react-form';
     5  import {RouteComponentProps} from 'react-router';
     6  
     7  import {CheckboxField, ConnectionStateIcon, DataLoader, EmptyState, ErrorNotification, NumberField, Page, Repo, Spinner} from '../../../shared/components';
     8  import {AppContext} from '../../../shared/context';
     9  import * as models from '../../../shared/models';
    10  import {services} from '../../../shared/services';
    11  import {RepoDetails} from '../repo-details/repo-details';
    12  
    13  require('./repos-list.scss');
    14  
    15  interface NewSSHRepoParams {
    16      type: string;
    17      name: string;
    18      url: string;
    19      sshPrivateKey: string;
    20      insecure: boolean;
    21      enableLfs: boolean;
    22      proxy: string;
    23      project?: string;
    24  }
    25  
    26  export interface NewHTTPSRepoParams {
    27      type: string;
    28      name: string;
    29      url: string;
    30      username: string;
    31      password: string;
    32      tlsClientCertData: string;
    33      tlsClientCertKey: string;
    34      insecure: boolean;
    35      enableLfs: boolean;
    36      proxy: string;
    37      project?: string;
    38      forceHttpBasicAuth?: boolean;
    39      enableOCI: boolean;
    40  }
    41  
    42  interface NewGitHubAppRepoParams {
    43      type: string;
    44      name: string;
    45      url: string;
    46      githubAppPrivateKey: string;
    47      githubAppId: bigint;
    48      githubAppInstallationId: bigint;
    49      githubAppEnterpriseBaseURL: string;
    50      tlsClientCertData: string;
    51      tlsClientCertKey: string;
    52      insecure: boolean;
    53      enableLfs: boolean;
    54      proxy: string;
    55      project?: string;
    56  }
    57  
    58  interface NewGoogleCloudSourceRepoParams {
    59      type: string;
    60      name: string;
    61      url: string;
    62      gcpServiceAccountKey: string;
    63      proxy: string;
    64      project?: string;
    65  }
    66  
    67  interface NewSSHRepoCredsParams {
    68      url: string;
    69      sshPrivateKey: string;
    70  }
    71  
    72  interface NewHTTPSRepoCredsParams {
    73      url: string;
    74      username: string;
    75      password: string;
    76      tlsClientCertData: string;
    77      tlsClientCertKey: string;
    78      proxy: string;
    79      forceHttpBasicAuth: boolean;
    80      enableOCI: boolean;
    81  }
    82  
    83  interface NewGitHubAppRepoCredsParams {
    84      url: string;
    85      githubAppPrivateKey: string;
    86      githubAppId: bigint;
    87      githubAppInstallationId: bigint;
    88      githubAppEnterpriseBaseURL: string;
    89      tlsClientCertData: string;
    90      tlsClientCertKey: string;
    91      proxy: string;
    92  }
    93  
    94  interface NewGoogleCloudSourceRepoCredsParams {
    95      url: string;
    96      gcpServiceAccountKey: string;
    97  }
    98  
    99  export enum ConnectionMethod {
   100      SSH = 'via SSH',
   101      HTTPS = 'via HTTPS',
   102      GITHUBAPP = 'via GitHub App',
   103      GOOGLECLOUD = 'via Google Cloud'
   104  }
   105  
   106  export class ReposList extends React.Component<
   107      RouteComponentProps<any>,
   108      {
   109          connecting: boolean;
   110          method: string;
   111          currentRepo: models.Repository;
   112          displayEditPanel: boolean;
   113      }
   114  > {
   115      public static contextTypes = {
   116          router: PropTypes.object,
   117          apis: PropTypes.object,
   118          history: PropTypes.object
   119      };
   120  
   121      private formApi: FormApi;
   122      private credsTemplate: boolean;
   123      private repoLoader: DataLoader;
   124      private credsLoader: DataLoader;
   125  
   126      constructor(props: RouteComponentProps<any>) {
   127          super(props);
   128          this.state = {
   129              connecting: false,
   130              method: ConnectionMethod.SSH,
   131              currentRepo: null,
   132              displayEditPanel: false
   133          };
   134      }
   135  
   136      private ConnectRepoFormButton(method: string, onSelection: (method: string) => void) {
   137          return (
   138              <div className='white-box'>
   139                  <p>Choose your connection method:</p>
   140                  <DropDownMenu
   141                      anchor={() => (
   142                          <p>
   143                              {method.toUpperCase()} <i className='fa fa-caret-down' />
   144                          </p>
   145                      )}
   146                      items={[ConnectionMethod.SSH, ConnectionMethod.HTTPS, ConnectionMethod.GITHUBAPP, ConnectionMethod.GOOGLECLOUD].map(
   147                          (connectMethod: ConnectionMethod.SSH | ConnectionMethod.HTTPS | ConnectionMethod.GITHUBAPP | ConnectionMethod.GOOGLECLOUD) => ({
   148                              title: connectMethod.toUpperCase(),
   149                              action: () => {
   150                                  onSelection(connectMethod);
   151                                  const formState = this.formApi.getFormState();
   152                                  this.formApi.setFormState({
   153                                      ...formState,
   154                                      errors: {}
   155                                  });
   156                              }
   157                          })
   158                      )}
   159                  />
   160              </div>
   161          );
   162      }
   163  
   164      private onChooseDefaultValues = (): FormValues => {
   165          return {type: 'git', ghType: 'GitHub'};
   166      };
   167  
   168      private onValidateErrors(params: FormValues): FormErrors {
   169          switch (this.state.method) {
   170              case ConnectionMethod.SSH:
   171                  const sshValues = params as NewSSHRepoParams;
   172                  return {
   173                      url: !sshValues.url && 'Repository URL is required'
   174                  };
   175              case ConnectionMethod.HTTPS:
   176                  const httpsValues = params as NewHTTPSRepoParams;
   177                  return {
   178                      url:
   179                          (!httpsValues.url && 'Repository URL is required') ||
   180                          (this.credsTemplate && !this.isHTTPSUrl(httpsValues.url) && !httpsValues.enableOCI && 'Not a valid HTTPS URL'),
   181                      name: httpsValues.type === 'helm' && !httpsValues.name && 'Name is required',
   182                      username: !httpsValues.username && httpsValues.password && 'Username is required if password is given.',
   183                      password: !httpsValues.password && httpsValues.username && 'Password is required if username is given.',
   184                      tlsClientCertKey: !httpsValues.tlsClientCertKey && httpsValues.tlsClientCertData && 'TLS client cert key is required if TLS client cert is given.'
   185                  };
   186              case ConnectionMethod.GITHUBAPP:
   187                  const githubAppValues = params as NewGitHubAppRepoParams;
   188                  return {
   189                      url: (!githubAppValues.url && 'Repository URL is required') || (this.credsTemplate && !this.isHTTPSUrl(githubAppValues.url) && 'Not a valid HTTPS URL'),
   190                      githubAppId: !githubAppValues.githubAppId && 'GitHub App ID is required',
   191                      githubAppInstallationId: !githubAppValues.githubAppInstallationId && 'GitHub App installation ID is required',
   192                      githubAppPrivateKey: !githubAppValues.githubAppPrivateKey && 'GitHub App private Key is required'
   193                  };
   194              case ConnectionMethod.GOOGLECLOUD:
   195                  const googleCloudValues = params as NewGoogleCloudSourceRepoParams;
   196                  return {
   197                      url: (!googleCloudValues.url && 'Repo URL is required') || (this.credsTemplate && !this.isHTTPSUrl(googleCloudValues.url) && 'Not a valid HTTPS URL'),
   198                      gcpServiceAccountKey: !googleCloudValues.gcpServiceAccountKey && 'GCP service account key is required'
   199                  };
   200          }
   201      }
   202  
   203      private SlidingPanelHeader() {
   204          return (
   205              <>
   206                  {this.showConnectRepo && (
   207                      <>
   208                          <button
   209                              className='argo-button argo-button--base'
   210                              onClick={() => {
   211                                  this.credsTemplate = false;
   212                                  this.formApi.submitForm(null);
   213                              }}>
   214                              <Spinner show={this.state.connecting} style={{marginRight: '5px'}} />
   215                              Connect
   216                          </button>{' '}
   217                          <button
   218                              className='argo-button argo-button--base'
   219                              onClick={() => {
   220                                  this.credsTemplate = true;
   221                                  this.formApi.submitForm(null);
   222                              }}>
   223                              Save as credentials template
   224                          </button>{' '}
   225                          <button onClick={() => (this.showConnectRepo = false)} className='argo-button argo-button--base-o'>
   226                              Cancel
   227                          </button>
   228                      </>
   229                  )}
   230                  {this.state.displayEditPanel && (
   231                      <button onClick={() => this.setState({displayEditPanel: false})} className='argo-button argo-button--base-o'>
   232                          Cancel
   233                      </button>
   234                  )}
   235              </>
   236          );
   237      }
   238  
   239      private onSubmitForm() {
   240          switch (this.state.method) {
   241              case ConnectionMethod.SSH:
   242                  return (params: FormValues) => this.connectSSHRepo(params as NewSSHRepoParams);
   243              case ConnectionMethod.HTTPS:
   244                  return (params: FormValues) => {
   245                      params.url = params.enableOCI ? this.stripProtocol(params.url) : params.url;
   246                      return this.connectHTTPSRepo(params as NewHTTPSRepoParams);
   247                  };
   248              case ConnectionMethod.GITHUBAPP:
   249                  return (params: FormValues) => this.connectGitHubAppRepo(params as NewGitHubAppRepoParams);
   250              case ConnectionMethod.GOOGLECLOUD:
   251                  return (params: FormValues) => this.connectGoogleCloudSourceRepo(params as NewGoogleCloudSourceRepoParams);
   252          }
   253      }
   254  
   255      public render() {
   256          return (
   257              <Page
   258                  title='Repositories'
   259                  toolbar={{
   260                      breadcrumbs: [{title: 'Settings', path: '/settings'}, {title: 'Repositories'}],
   261                      actionMenu: {
   262                          items: [
   263                              {
   264                                  iconClassName: 'fa fa-plus',
   265                                  title: 'Connect Repo',
   266                                  action: () => (this.showConnectRepo = true)
   267                              },
   268                              {
   269                                  iconClassName: 'fa fa-redo',
   270                                  title: 'Refresh list',
   271                                  action: () => {
   272                                      this.refreshRepoList();
   273                                  }
   274                              }
   275                          ]
   276                      }
   277                  }}>
   278                  <div className='repos-list'>
   279                      <div className='argo-container'>
   280                          <DataLoader load={() => services.repos.list()} ref={loader => (this.repoLoader = loader)}>
   281                              {(repos: models.Repository[]) =>
   282                                  (repos.length > 0 && (
   283                                      <div className='argo-table-list'>
   284                                          <div className='argo-table-list__head'>
   285                                              <div className='row'>
   286                                                  <div className='columns small-1' />
   287                                                  <div className='columns small-1'>TYPE</div>
   288                                                  <div className='columns small-2'>NAME</div>
   289                                                  <div className='columns small-5'>REPOSITORY</div>
   290                                                  <div className='columns small-3'>CONNECTION STATUS</div>
   291                                              </div>
   292                                          </div>
   293                                          {repos.map(repo => (
   294                                              <div
   295                                                  className={`argo-table-list__row ${this.isRepoUpdatable(repo) ? 'item-clickable' : ''}`}
   296                                                  key={repo.repo}
   297                                                  onClick={() => (this.isRepoUpdatable(repo) ? this.displayEditSliding(repo) : null)}>
   298                                                  <div className='row'>
   299                                                      <div className='columns small-1'>
   300                                                          <i className={'icon argo-icon-' + (repo.type || 'git')} />
   301                                                      </div>
   302                                                      <div className='columns small-1'>
   303                                                          <span>{repo.type || 'git'}</span>
   304                                                          {repo.enableOCI && <span> OCI</span>}
   305                                                      </div>
   306                                                      <div className='columns small-2'>
   307                                                          <Tooltip content={repo.name}>
   308                                                              <span>{repo.name}</span>
   309                                                          </Tooltip>
   310                                                      </div>
   311                                                      <div className='columns small-5'>
   312                                                          <Tooltip content={repo.repo}>
   313                                                              <span>
   314                                                                  <Repo url={repo.repo} />
   315                                                              </span>
   316                                                          </Tooltip>
   317                                                      </div>
   318                                                      <div className='columns small-3'>
   319                                                          <ConnectionStateIcon state={repo.connectionState} /> {repo.connectionState.status}
   320                                                          <DropDownMenu
   321                                                              anchor={() => (
   322                                                                  <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
   323                                                                      <i className='fa fa-ellipsis-v' />
   324                                                                  </button>
   325                                                              )}
   326                                                              items={[
   327                                                                  {
   328                                                                      title: 'Create application',
   329                                                                      action: () =>
   330                                                                          this.appContext.apis.navigation.goto('/applications', {
   331                                                                              new: JSON.stringify({spec: {source: {repoURL: repo.repo}}})
   332                                                                          })
   333                                                                  },
   334                                                                  {
   335                                                                      title: 'Disconnect',
   336                                                                      action: () => this.disconnectRepo(repo.repo)
   337                                                                  }
   338                                                              ]}
   339                                                          />
   340                                                      </div>
   341                                                  </div>
   342                                              </div>
   343                                          ))}
   344                                      </div>
   345                                  )) || (
   346                                      <EmptyState icon='argo-icon-git'>
   347                                          <h4>No repositories connected</h4>
   348                                          <h5>Connect your repo to deploy apps.</h5>
   349                                      </EmptyState>
   350                                  )
   351                              }
   352                          </DataLoader>
   353                      </div>
   354                      <div className='argo-container'>
   355                          <DataLoader load={() => services.repocreds.list()} ref={loader => (this.credsLoader = loader)}>
   356                              {(creds: models.RepoCreds[]) =>
   357                                  creds.length > 0 && (
   358                                      <div className='argo-table-list'>
   359                                          <div className='argo-table-list__head'>
   360                                              <div className='row'>
   361                                                  <div className='columns small-9'>CREDENTIALS TEMPLATE URL</div>
   362                                                  <div className='columns small-3'>CREDS</div>
   363                                              </div>
   364                                          </div>
   365                                          {creds.map(repo => (
   366                                              <div className='argo-table-list__row' key={repo.url}>
   367                                                  <div className='row'>
   368                                                      <div className='columns small-9'>
   369                                                          <i className='icon argo-icon-git' /> <Repo url={repo.url} />
   370                                                      </div>
   371                                                      <div className='columns small-3'>
   372                                                          -
   373                                                          <DropDownMenu
   374                                                              anchor={() => (
   375                                                                  <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
   376                                                                      <i className='fa fa-ellipsis-v' />
   377                                                                  </button>
   378                                                              )}
   379                                                              items={[{title: 'Remove', action: () => this.removeRepoCreds(repo.url)}]}
   380                                                          />
   381                                                      </div>
   382                                                  </div>
   383                                              </div>
   384                                          ))}
   385                                      </div>
   386                                  )
   387                              }
   388                          </DataLoader>
   389                      </div>
   390                  </div>
   391                  <SlidingPanel
   392                      isShown={this.showConnectRepo || this.state.displayEditPanel}
   393                      onClose={() => {
   394                          if (!this.state.displayEditPanel && this.showConnectRepo) {
   395                              this.showConnectRepo = false;
   396                          }
   397                          if (this.state.displayEditPanel) {
   398                              this.setState({displayEditPanel: false});
   399                          }
   400                      }}
   401                      header={this.SlidingPanelHeader()}>
   402                      {this.showConnectRepo &&
   403                          this.ConnectRepoFormButton(this.state.method, method => {
   404                              this.setState({method});
   405                          })}
   406                      {this.state.displayEditPanel && <RepoDetails repo={this.state.currentRepo} save={(params: NewHTTPSRepoParams) => this.updateHTTPSRepo(params)} />}
   407                      {!this.state.displayEditPanel && (
   408                          <DataLoader load={() => services.projects.list('items.metadata.name').then(projects => projects.map(proj => proj.metadata.name).sort())}>
   409                              {projects => (
   410                                  <Form
   411                                      onSubmit={this.onSubmitForm()}
   412                                      getApi={api => (this.formApi = api)}
   413                                      defaultValues={this.onChooseDefaultValues()}
   414                                      validateError={(values: FormValues) => this.onValidateErrors(values)}>
   415                                      {formApi => (
   416                                          <form onSubmit={formApi.submitForm} role='form' className='repos-list width-control'>
   417                                              {this.state.method === ConnectionMethod.SSH && (
   418                                                  <div className='white-box'>
   419                                                      <p>CONNECT REPO USING SSH</p>
   420                                                      <div className='argo-form-row'>
   421                                                          <FormField formApi={formApi} label='Name (mandatory for Helm)' field='name' component={Text} />
   422                                                      </div>
   423                                                      <div className='argo-form-row'>
   424                                                          <FormField
   425                                                              formApi={formApi}
   426                                                              label='Project'
   427                                                              field='project'
   428                                                              component={AutocompleteField}
   429                                                              componentProps={{items: projects}}
   430                                                          />
   431                                                      </div>
   432                                                      <div className='argo-form-row'>
   433                                                          <FormField formApi={formApi} label='Repository URL' field='url' component={Text} />
   434                                                      </div>
   435                                                      <div className='argo-form-row'>
   436                                                          <FormField formApi={formApi} label='SSH private key data' field='sshPrivateKey' component={TextArea} />
   437                                                      </div>
   438                                                      <div className='argo-form-row'>
   439                                                          <FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} />
   440                                                          <HelpIcon title='This setting is ignored when creating as credential template.' />
   441                                                      </div>
   442                                                      <div className='argo-form-row'>
   443                                                          <FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} />
   444                                                          <HelpIcon title='This setting is ignored when creating as credential template.' />
   445                                                      </div>
   446                                                      <div className='argo-form-row'>
   447                                                          <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} />
   448                                                      </div>
   449                                                  </div>
   450                                              )}
   451                                              {this.state.method === ConnectionMethod.HTTPS && (
   452                                                  <div className='white-box'>
   453                                                      <p>CONNECT REPO USING HTTPS</p>
   454                                                      <div className='argo-form-row'>
   455                                                          <FormField formApi={formApi} label='Type' field='type' component={FormSelect} componentProps={{options: ['git', 'helm']}} />
   456                                                      </div>
   457                                                      {formApi.getFormState().values.type === 'helm' && (
   458                                                          <div className='argo-form-row'>
   459                                                              <FormField formApi={formApi} label='Name' field='name' component={Text} />
   460                                                          </div>
   461                                                      )}
   462                                                      <div className='argo-form-row'>
   463                                                          <FormField
   464                                                              formApi={formApi}
   465                                                              label='Project'
   466                                                              field='project'
   467                                                              component={AutocompleteField}
   468                                                              componentProps={{items: projects}}
   469                                                          />
   470                                                      </div>
   471                                                      <div className='argo-form-row'>
   472                                                          <FormField formApi={formApi} label='Repository URL' field='url' component={Text} />
   473                                                      </div>
   474                                                      <div className='argo-form-row'>
   475                                                          <FormField formApi={formApi} label='Username (optional)' field='username' component={Text} />
   476                                                      </div>
   477                                                      <div className='argo-form-row'>
   478                                                          <FormField
   479                                                              formApi={formApi}
   480                                                              label='Password (optional)'
   481                                                              field='password'
   482                                                              component={Text}
   483                                                              componentProps={{type: 'password'}}
   484                                                          />
   485                                                      </div>
   486                                                      <div className='argo-form-row'>
   487                                                          <FormField formApi={formApi} label='TLS client certificate (optional)' field='tlsClientCertData' component={TextArea} />
   488                                                      </div>
   489                                                      <div className='argo-form-row'>
   490                                                          <FormField formApi={formApi} label='TLS client certificate key (optional)' field='tlsClientCertKey' component={TextArea} />
   491                                                      </div>
   492                                                      {formApi.getFormState().values.type === 'git' && (
   493                                                          <React.Fragment>
   494                                                              <div className='argo-form-row'>
   495                                                                  <FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} />
   496                                                                  <HelpIcon title='This setting is ignored when creating as credential template.' />
   497                                                              </div>
   498                                                              <div className='argo-form-row'>
   499                                                                  <FormField formApi={formApi} label='Force HTTP basic auth' field='forceHttpBasicAuth' component={CheckboxField} />
   500                                                              </div>
   501                                                              <div className='argo-form-row'>
   502                                                                  <FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} />
   503                                                                  <HelpIcon title='This setting is ignored when creating as credential template.' />
   504                                                              </div>
   505                                                          </React.Fragment>
   506                                                      )}
   507                                                      <div className='argo-form-row'>
   508                                                          <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} />
   509                                                      </div>
   510                                                      <div className='argo-form-row'>
   511                                                          <FormField formApi={formApi} label='Enable OCI' field='enableOCI' component={CheckboxField} />
   512                                                      </div>
   513                                                  </div>
   514                                              )}
   515                                              {this.state.method === ConnectionMethod.GITHUBAPP && (
   516                                                  <div className='white-box'>
   517                                                      <p>CONNECT REPO USING GITHUB APP</p>
   518                                                      <div className='argo-form-row'>
   519                                                          <FormField
   520                                                              formApi={formApi}
   521                                                              label='Type'
   522                                                              field='ghType'
   523                                                              component={FormSelect}
   524                                                              componentProps={{options: ['GitHub', 'GitHub Enterprise']}}
   525                                                          />
   526                                                      </div>
   527                                                      {formApi.getFormState().values.ghType === 'GitHub Enterprise' && (
   528                                                          <React.Fragment>
   529                                                              <div className='argo-form-row'>
   530                                                                  <FormField
   531                                                                      formApi={formApi}
   532                                                                      label='GitHub Enterprise Base URL (e.g. https://ghe.example.com/api/v3)'
   533                                                                      field='githubAppEnterpriseBaseURL'
   534                                                                      component={Text}
   535                                                                  />
   536                                                              </div>
   537                                                          </React.Fragment>
   538                                                      )}
   539                                                      <div className='argo-form-row'>
   540                                                          <FormField
   541                                                              formApi={formApi}
   542                                                              label='Project'
   543                                                              field='project'
   544                                                              component={AutocompleteField}
   545                                                              componentProps={{items: projects}}
   546                                                          />
   547                                                      </div>
   548                                                      <div className='argo-form-row'>
   549                                                          <FormField formApi={formApi} label='Repository URL' field='url' component={Text} />
   550                                                      </div>
   551                                                      <div className='argo-form-row'>
   552                                                          <FormField formApi={formApi} label='GitHub App ID' field='githubAppId' component={NumberField} />
   553                                                      </div>
   554                                                      <div className='argo-form-row'>
   555                                                          <FormField formApi={formApi} label='GitHub App Installation ID' field='githubAppInstallationId' component={NumberField} />
   556                                                      </div>
   557                                                      <div className='argo-form-row'>
   558                                                          <FormField formApi={formApi} label='GitHub App private key' field='githubAppPrivateKey' component={TextArea} />
   559                                                      </div>
   560                                                      <div className='argo-form-row'>
   561                                                          <FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} />
   562                                                          <HelpIcon title='This setting is ignored when creating as credential template.' />
   563                                                      </div>
   564                                                      <div className='argo-form-row'>
   565                                                          <FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} />
   566                                                          <HelpIcon title='This setting is ignored when creating as credential template.' />
   567                                                      </div>
   568                                                      {formApi.getFormState().values.ghType === 'GitHub Enterprise' && (
   569                                                          <React.Fragment>
   570                                                              <div className='argo-form-row'>
   571                                                                  <FormField
   572                                                                      formApi={formApi}
   573                                                                      label='TLS client certificate (optional)'
   574                                                                      field='tlsClientCertData'
   575                                                                      component={TextArea}
   576                                                                  />
   577                                                              </div>
   578                                                              <div className='argo-form-row'>
   579                                                                  <FormField
   580                                                                      formApi={formApi}
   581                                                                      label='TLS client certificate key (optional)'
   582                                                                      field='tlsClientCertKey'
   583                                                                      component={TextArea}
   584                                                                  />
   585                                                              </div>
   586                                                          </React.Fragment>
   587                                                      )}
   588                                                      <div className='argo-form-row'>
   589                                                          <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} />
   590                                                      </div>
   591                                                  </div>
   592                                              )}
   593                                              {this.state.method === ConnectionMethod.GOOGLECLOUD && (
   594                                                  <div className='white-box'>
   595                                                      <p>CONNECT REPO USING GOOGLE CLOUD</p>
   596                                                      <div className='argo-form-row'>
   597                                                          <FormField
   598                                                              formApi={formApi}
   599                                                              label='Project'
   600                                                              field='project'
   601                                                              component={AutocompleteField}
   602                                                              componentProps={{items: projects}}
   603                                                          />
   604                                                      </div>
   605                                                      <div className='argo-form-row'>
   606                                                          <FormField formApi={formApi} label='Repository URL' field='url' component={Text} />
   607                                                      </div>
   608                                                      <div className='argo-form-row'>
   609                                                          <FormField formApi={formApi} label='GCP service account key' field='gcpServiceAccountKey' component={TextArea} />
   610                                                      </div>
   611                                                      <div className='argo-form-row'>
   612                                                          <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} />
   613                                                      </div>
   614                                                  </div>
   615                                              )}
   616                                          </form>
   617                                      )}
   618                                  </Form>
   619                              )}
   620                          </DataLoader>
   621                      )}
   622                  </SlidingPanel>
   623              </Page>
   624          );
   625      }
   626  
   627      private displayEditSliding(repo: models.Repository) {
   628          this.setState({currentRepo: repo});
   629          this.setState({displayEditPanel: true});
   630      }
   631  
   632      // Whether url is a https url (simple version)
   633      private isHTTPSUrl(url: string) {
   634          if (url.match(/^https:\/\/.*$/gi)) {
   635              return true;
   636          } else {
   637              return false;
   638          }
   639      }
   640  
   641      private stripProtocol(url: string) {
   642          return url.replace('https://', '').replace('oci://', '');
   643      }
   644  
   645      // only connections of git type which is not via GitHub App are updatable
   646      private isRepoUpdatable(repo: models.Repository) {
   647          return this.isHTTPSUrl(repo.repo) && repo.type === 'git' && !repo.githubAppId;
   648      }
   649  
   650      // Forces a reload of configured repositories, circumventing the cache
   651      private async refreshRepoList(updatedRepo?: string) {
   652          try {
   653              await services.repos.listNoCache();
   654              await services.repocreds.list();
   655              this.repoLoader.reload();
   656              this.appContext.apis.notifications.show({
   657                  content: updatedRepo ? `Successfully updated ${updatedRepo} repository` : 'Successfully reloaded list of repositories',
   658                  type: NotificationType.Success
   659              });
   660          } catch (e) {
   661              this.appContext.apis.notifications.show({
   662                  content: <ErrorNotification title='Could not refresh list of repositories' e={e} />,
   663                  type: NotificationType.Error
   664              });
   665          }
   666      }
   667  
   668      // Empty all fields in connect repository form
   669      private clearConnectRepoForm() {
   670          this.credsTemplate = false;
   671          this.formApi.resetAll();
   672      }
   673  
   674      // Connect a new repository or create a repository credentials for SSH repositories
   675      private async connectSSHRepo(params: NewSSHRepoParams) {
   676          if (this.credsTemplate) {
   677              this.createSSHCreds({url: params.url, sshPrivateKey: params.sshPrivateKey});
   678          } else {
   679              this.setState({connecting: true});
   680              try {
   681                  await services.repos.createSSH(params);
   682                  this.repoLoader.reload();
   683                  this.showConnectRepo = false;
   684              } catch (e) {
   685                  this.appContext.apis.notifications.show({
   686                      content: <ErrorNotification title='Unable to connect SSH repository' e={e} />,
   687                      type: NotificationType.Error
   688                  });
   689              } finally {
   690                  this.setState({connecting: false});
   691              }
   692          }
   693      }
   694  
   695      // Connect a new repository or create a repository credentials for HTTPS repositories
   696      private async connectHTTPSRepo(params: NewHTTPSRepoParams) {
   697          if (this.credsTemplate) {
   698              this.createHTTPSCreds({
   699                  url: params.url,
   700                  username: params.username,
   701                  password: params.password,
   702                  tlsClientCertData: params.tlsClientCertData,
   703                  tlsClientCertKey: params.tlsClientCertKey,
   704                  proxy: params.proxy,
   705                  forceHttpBasicAuth: params.forceHttpBasicAuth,
   706                  enableOCI: params.enableOCI
   707              });
   708          } else {
   709              this.setState({connecting: true});
   710              try {
   711                  await services.repos.createHTTPS(params);
   712                  this.repoLoader.reload();
   713                  this.showConnectRepo = false;
   714              } catch (e) {
   715                  this.appContext.apis.notifications.show({
   716                      content: <ErrorNotification title='Unable to connect HTTPS repository' e={e} />,
   717                      type: NotificationType.Error
   718                  });
   719              } finally {
   720                  this.setState({connecting: false});
   721              }
   722          }
   723      }
   724  
   725      // Update an existing repository for HTTPS repositories
   726      private async updateHTTPSRepo(params: NewHTTPSRepoParams) {
   727          try {
   728              await services.repos.updateHTTPS(params);
   729              this.repoLoader.reload();
   730              this.setState({displayEditPanel: false});
   731              this.refreshRepoList(params.url);
   732          } catch (e) {
   733              this.appContext.apis.notifications.show({
   734                  content: <ErrorNotification title='Unable to update HTTPS repository' e={e} />,
   735                  type: NotificationType.Error
   736              });
   737          } finally {
   738              this.setState({connecting: false});
   739          }
   740      }
   741  
   742      // Connect a new repository or create a repository credentials for GitHub App repositories
   743      private async connectGitHubAppRepo(params: NewGitHubAppRepoParams) {
   744          if (this.credsTemplate) {
   745              this.createGitHubAppCreds({
   746                  url: params.url,
   747                  githubAppPrivateKey: params.githubAppPrivateKey,
   748                  githubAppId: params.githubAppId,
   749                  githubAppInstallationId: params.githubAppInstallationId,
   750                  githubAppEnterpriseBaseURL: params.githubAppEnterpriseBaseURL,
   751                  tlsClientCertData: params.tlsClientCertData,
   752                  tlsClientCertKey: params.tlsClientCertKey,
   753                  proxy: params.proxy
   754              });
   755          } else {
   756              this.setState({connecting: true});
   757              try {
   758                  await services.repos.createGitHubApp(params);
   759                  this.repoLoader.reload();
   760                  this.showConnectRepo = false;
   761              } catch (e) {
   762                  this.appContext.apis.notifications.show({
   763                      content: <ErrorNotification title='Unable to connect GitHub App repository' e={e} />,
   764                      type: NotificationType.Error
   765                  });
   766              } finally {
   767                  this.setState({connecting: false});
   768              }
   769          }
   770      }
   771  
   772      // Connect a new repository or create a repository credentials for GitHub App repositories
   773      private async connectGoogleCloudSourceRepo(params: NewGoogleCloudSourceRepoParams) {
   774          if (this.credsTemplate) {
   775              this.createGoogleCloudSourceCreds({
   776                  url: params.url,
   777                  gcpServiceAccountKey: params.gcpServiceAccountKey
   778              });
   779          } else {
   780              this.setState({connecting: true});
   781              try {
   782                  await services.repos.createGoogleCloudSource(params);
   783                  this.repoLoader.reload();
   784                  this.showConnectRepo = false;
   785              } catch (e) {
   786                  this.appContext.apis.notifications.show({
   787                      content: <ErrorNotification title='Unable to connect Google Cloud Source repository' e={e} />,
   788                      type: NotificationType.Error
   789                  });
   790              } finally {
   791                  this.setState({connecting: false});
   792              }
   793          }
   794      }
   795  
   796      private async createHTTPSCreds(params: NewHTTPSRepoCredsParams) {
   797          try {
   798              await services.repocreds.createHTTPS(params);
   799              this.credsLoader.reload();
   800              this.showConnectRepo = false;
   801          } catch (e) {
   802              this.appContext.apis.notifications.show({
   803                  content: <ErrorNotification title='Unable to create HTTPS credentials' e={e} />,
   804                  type: NotificationType.Error
   805              });
   806          }
   807      }
   808  
   809      private async createSSHCreds(params: NewSSHRepoCredsParams) {
   810          try {
   811              await services.repocreds.createSSH(params);
   812              this.credsLoader.reload();
   813              this.showConnectRepo = false;
   814          } catch (e) {
   815              this.appContext.apis.notifications.show({
   816                  content: <ErrorNotification title='Unable to create SSH credentials' e={e} />,
   817                  type: NotificationType.Error
   818              });
   819          }
   820      }
   821  
   822      private async createGitHubAppCreds(params: NewGitHubAppRepoCredsParams) {
   823          try {
   824              await services.repocreds.createGitHubApp(params);
   825              this.credsLoader.reload();
   826              this.showConnectRepo = false;
   827          } catch (e) {
   828              this.appContext.apis.notifications.show({
   829                  content: <ErrorNotification title='Unable to create GitHub App credentials' e={e} />,
   830                  type: NotificationType.Error
   831              });
   832          }
   833      }
   834  
   835      private async createGoogleCloudSourceCreds(params: NewGoogleCloudSourceRepoCredsParams) {
   836          try {
   837              await services.repocreds.createGoogleCloudSource(params);
   838              this.credsLoader.reload();
   839              this.showConnectRepo = false;
   840          } catch (e) {
   841              this.appContext.apis.notifications.show({
   842                  content: <ErrorNotification title='Unable to create Google Cloud Source credentials' e={e} />,
   843                  type: NotificationType.Error
   844              });
   845          }
   846      }
   847  
   848      // Remove a repository from the configuration
   849      private async disconnectRepo(repo: string) {
   850          const confirmed = await this.appContext.apis.popup.confirm('Disconnect repository', `Are you sure you want to disconnect '${repo}'?`);
   851          if (confirmed) {
   852              try {
   853                  await services.repos.delete(repo);
   854                  this.repoLoader.reload();
   855              } catch (e) {
   856                  this.appContext.apis.notifications.show({
   857                      content: <ErrorNotification title='Unable to disconnect repository' e={e} />,
   858                      type: NotificationType.Error
   859                  });
   860              }
   861          }
   862      }
   863  
   864      // Remove repository credentials from the configuration
   865      private async removeRepoCreds(url: string) {
   866          const confirmed = await this.appContext.apis.popup.confirm('Remove repository credentials', `Are you sure you want to remove credentials for URL prefix '${url}'?`);
   867          if (confirmed) {
   868              try {
   869                  await services.repocreds.delete(url);
   870                  this.credsLoader.reload();
   871              } catch (e) {
   872                  this.appContext.apis.notifications.show({
   873                      content: <ErrorNotification title='Unable to remove repository credentials' e={e} />,
   874                      type: NotificationType.Error
   875                  });
   876              }
   877          }
   878      }
   879  
   880      // Whether to show the new repository connection dialogue on the page
   881      private get showConnectRepo() {
   882          return new URLSearchParams(this.props.location.search).get('addRepo') === 'true';
   883      }
   884  
   885      private set showConnectRepo(val: boolean) {
   886          this.clearConnectRepoForm();
   887          this.appContext.router.history.push(`${this.props.match.url}?addRepo=${val}`);
   888      }
   889  
   890      private get appContext(): AppContext {
   891          return this.context as AppContext;
   892      }
   893  }