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

     1  /* eslint-disable no-case-declarations */
     2  import {AutocompleteField, DropDownMenu, FormField, FormSelect, HelpIcon, NotificationType, SlidingPanel, Tooltip} from 'argo-ui';
     3  import * as PropTypes from 'prop-types';
     4  import * as React from 'react';
     5  import {Form, FormValues, FormApi, Text, TextArea, FormErrors} from 'react-form';
     6  import {RouteComponentProps} from 'react-router';
     7  
     8  import {CheckboxField, ConnectionStateIcon, DataLoader, EmptyState, ErrorNotification, NumberField, Page, Repo, Spinner} from '../../../shared/components';
     9  import {AppContext} from '../../../shared/context';
    10  import * as models from '../../../shared/models';
    11  import {services} from '../../../shared/services';
    12  import {RepoDetails} from '../repo-details/repo-details';
    13  
    14  require('./repos-list.scss');
    15  
    16  interface NewSSHRepoParams {
    17      type: string;
    18      name: string;
    19      url: string;
    20      sshPrivateKey: string;
    21      insecure: boolean;
    22      enableLfs: boolean;
    23      proxy: string;
    24      noProxy: string;
    25      project?: string;
    26      // write should be true if saving as a write credential.
    27      write: boolean;
    28  }
    29  
    30  export interface NewHTTPSRepoParams {
    31      type: string;
    32      name: string;
    33      url: string;
    34      username: string;
    35      password: string;
    36      bearerToken: string;
    37      tlsClientCertData: string;
    38      tlsClientCertKey: string;
    39      insecure: boolean;
    40      enableLfs: boolean;
    41      proxy: string;
    42      noProxy: string;
    43      project?: string;
    44      forceHttpBasicAuth?: boolean;
    45      enableOCI: boolean;
    46      insecureOCIForceHttp: boolean;
    47      // write should be true if saving as a write credential.
    48      write: boolean;
    49      useAzureWorkloadIdentity: boolean;
    50  }
    51  
    52  interface NewGitHubAppRepoParams {
    53      type: string;
    54      name: string;
    55      url: string;
    56      githubAppPrivateKey: string;
    57      githubAppId: bigint;
    58      githubAppInstallationId: bigint;
    59      githubAppEnterpriseBaseURL: string;
    60      tlsClientCertData: string;
    61      tlsClientCertKey: string;
    62      insecure: boolean;
    63      enableLfs: boolean;
    64      proxy: string;
    65      noProxy: string;
    66      project?: string;
    67      // write should be true if saving as a write credential.
    68      write: boolean;
    69  }
    70  
    71  interface NewGoogleCloudSourceRepoParams {
    72      type: string;
    73      name: string;
    74      url: string;
    75      gcpServiceAccountKey: string;
    76      proxy: string;
    77      noProxy: string;
    78      project?: string;
    79      // write should be true if saving as a write credential.
    80      write: boolean;
    81  }
    82  
    83  interface NewSSHRepoCredsParams {
    84      url: string;
    85      sshPrivateKey: string;
    86      // write should be true if saving as a write credential.
    87      write: boolean;
    88  }
    89  
    90  interface NewHTTPSRepoCredsParams {
    91      url: string;
    92      type: string;
    93      username: string;
    94      password: string;
    95      bearerToken: string;
    96      tlsClientCertData: string;
    97      tlsClientCertKey: string;
    98      proxy: string;
    99      noProxy: string;
   100      forceHttpBasicAuth: boolean;
   101      enableOCI: boolean;
   102      insecureOCIForceHttp: boolean;
   103      // write should be true if saving as a write credential.
   104      write: boolean;
   105      useAzureWorkloadIdentity: boolean;
   106  }
   107  
   108  interface NewGitHubAppRepoCredsParams {
   109      url: string;
   110      githubAppPrivateKey: string;
   111      githubAppId: bigint;
   112      githubAppInstallationId: bigint;
   113      githubAppEnterpriseBaseURL: string;
   114      tlsClientCertData: string;
   115      tlsClientCertKey: string;
   116      proxy: string;
   117      noProxy: string;
   118      // write should be true if saving as a write credential.
   119      write: boolean;
   120  }
   121  
   122  interface NewGoogleCloudSourceRepoCredsParams {
   123      url: string;
   124      gcpServiceAccountKey: string;
   125      // write should be true if saving as a write credential.
   126      write: boolean;
   127  }
   128  
   129  export enum ConnectionMethod {
   130      SSH = 'via SSH',
   131      HTTPS = 'via HTTP/HTTPS',
   132      GITHUBAPP = 'via GitHub App',
   133      GOOGLECLOUD = 'via Google Cloud'
   134  }
   135  
   136  export class ReposList extends React.Component<
   137      RouteComponentProps<any>,
   138      {
   139          connecting: boolean;
   140          method: string;
   141          currentRepo: models.Repository;
   142          displayEditPanel: boolean;
   143          authSettings: models.AuthSettings;
   144          statusProperty: 'all' | 'Successful' | 'Failed' | 'Unknown';
   145          projectProperty: string;
   146          typeProperty: 'all' | 'git' | 'helm';
   147          name: string;
   148      }
   149  > {
   150      public static contextTypes = {
   151          router: PropTypes.object,
   152          apis: PropTypes.object,
   153          history: PropTypes.object
   154      };
   155  
   156      private formApi: FormApi;
   157      private credsTemplate: boolean;
   158      private repoLoader: DataLoader;
   159      private credsLoader: DataLoader;
   160  
   161      constructor(props: RouteComponentProps<any>) {
   162          super(props);
   163          this.state = {
   164              connecting: false,
   165              method: ConnectionMethod.SSH,
   166              currentRepo: null,
   167              displayEditPanel: false,
   168              authSettings: null,
   169              statusProperty: 'all',
   170              projectProperty: 'all',
   171              typeProperty: 'all',
   172              name: ''
   173          };
   174      }
   175  
   176      public async componentDidMount() {
   177          this.setState({
   178              authSettings: await services.authService.settings()
   179          });
   180      }
   181  
   182      private ConnectRepoFormButton(method: string, onSelection: (method: string) => void) {
   183          return (
   184              <div className='white-box'>
   185                  <p>Choose your connection method:</p>
   186                  <DropDownMenu
   187                      anchor={() => (
   188                          <p>
   189                              {method.toUpperCase()} <i className='fa fa-caret-down' />
   190                          </p>
   191                      )}
   192                      items={[ConnectionMethod.SSH, ConnectionMethod.HTTPS, ConnectionMethod.GITHUBAPP, ConnectionMethod.GOOGLECLOUD].map(
   193                          (connectMethod: ConnectionMethod.SSH | ConnectionMethod.HTTPS | ConnectionMethod.GITHUBAPP | ConnectionMethod.GOOGLECLOUD) => ({
   194                              title: connectMethod.toUpperCase(),
   195                              action: () => {
   196                                  onSelection(connectMethod);
   197                                  const formState = this.formApi.getFormState();
   198                                  this.formApi.setFormState({
   199                                      ...formState,
   200                                      errors: {}
   201                                  });
   202                              }
   203                          })
   204                      )}
   205                  />
   206              </div>
   207          );
   208      }
   209  
   210      private onChooseDefaultValues = (): FormValues => {
   211          return {type: 'git', ghType: 'GitHub', write: false};
   212      };
   213  
   214      private onValidateErrors(params: FormValues): FormErrors {
   215          switch (this.state.method) {
   216              case ConnectionMethod.SSH:
   217                  const sshValues = params as NewSSHRepoParams;
   218                  return {
   219                      url: !sshValues.url && 'Repository URL is required'
   220                  };
   221              case ConnectionMethod.HTTPS:
   222                  const validURLValues = params as NewHTTPSRepoParams;
   223                  return {
   224                      url:
   225                          (!validURLValues.url && 'Repository URL is required') ||
   226                          (this.credsTemplate && !this.isHTTPOrHTTPSUrl(validURLValues.url) && !validURLValues.enableOCI && params.type != 'oci' && 'Not a valid HTTP/HTTPS URL') ||
   227                          (this.credsTemplate && !this.isOCIUrl(validURLValues.url) && params.type == 'oci' && 'Not a valid OCI URL'),
   228                      name: validURLValues.type === 'helm' && !validURLValues.name && 'Name is required',
   229                      username: !validURLValues.username && validURLValues.password && 'Username is required if password is given.',
   230                      password: !validURLValues.password && validURLValues.username && 'Password is required if username is given.',
   231                      tlsClientCertKey: !validURLValues.tlsClientCertKey && validURLValues.tlsClientCertData && 'TLS client cert key is required if TLS client cert is given.',
   232                      bearerToken:
   233                          (validURLValues.password && validURLValues.bearerToken && 'Either the password or the bearer token must be set, but not both.') ||
   234                          (validURLValues.bearerToken && validURLValues.type != 'git' && 'Bearer token is only supported for Git BitBucket Data Center repositories.')
   235                  };
   236              case ConnectionMethod.GITHUBAPP:
   237                  const githubAppValues = params as NewGitHubAppRepoParams;
   238                  return {
   239                      url:
   240                          (!githubAppValues.url && 'Repository URL is required') ||
   241                          (this.credsTemplate && !this.isHTTPOrHTTPSUrl(githubAppValues.url) && 'Not a valid HTTP/HTTPS URL'),
   242                      githubAppId: !githubAppValues.githubAppId && 'GitHub App ID is required',
   243                      githubAppInstallationId: !githubAppValues.githubAppInstallationId && 'GitHub App installation ID is required',
   244                      githubAppPrivateKey: !githubAppValues.githubAppPrivateKey && 'GitHub App private Key is required'
   245                  };
   246              case ConnectionMethod.GOOGLECLOUD:
   247                  const googleCloudValues = params as NewGoogleCloudSourceRepoParams;
   248                  return {
   249                      url:
   250                          (!googleCloudValues.url && 'Repo URL is required') || (this.credsTemplate && !this.isHTTPOrHTTPSUrl(googleCloudValues.url) && 'Not a valid HTTP/HTTPS URL'),
   251                      gcpServiceAccountKey: !googleCloudValues.gcpServiceAccountKey && 'GCP service account key is required'
   252                  };
   253          }
   254      }
   255  
   256      private SlidingPanelHeader() {
   257          return (
   258              <>
   259                  {this.showConnectRepo && (
   260                      <>
   261                          <button
   262                              className='argo-button argo-button--base'
   263                              onClick={() => {
   264                                  this.credsTemplate = false;
   265                                  this.formApi.submitForm(null);
   266                              }}>
   267                              <Spinner show={this.state.connecting} style={{marginRight: '5px'}} />
   268                              Connect
   269                          </button>{' '}
   270                          <button
   271                              className='argo-button argo-button--base'
   272                              onClick={() => {
   273                                  this.credsTemplate = true;
   274                                  this.formApi.submitForm(null);
   275                              }}>
   276                              Save as credentials template
   277                          </button>{' '}
   278                          <button onClick={() => (this.showConnectRepo = false)} className='argo-button argo-button--base-o'>
   279                              Cancel
   280                          </button>
   281                      </>
   282                  )}
   283                  {this.state.displayEditPanel && (
   284                      <button onClick={() => this.setState({displayEditPanel: false})} className='argo-button argo-button--base-o'>
   285                          Cancel
   286                      </button>
   287                  )}
   288              </>
   289          );
   290      }
   291  
   292      private onSubmitForm() {
   293          switch (this.state.method) {
   294              case ConnectionMethod.SSH:
   295                  return (params: FormValues) => this.connectSSHRepo(params as NewSSHRepoParams);
   296              case ConnectionMethod.HTTPS:
   297                  return (params: FormValues) => {
   298                      params.url = params.enableOCI && params.type != 'oci' ? this.stripProtocol(params.url) : params.url;
   299                      return this.connectHTTPSRepo(params as NewHTTPSRepoParams);
   300                  };
   301              case ConnectionMethod.GITHUBAPP:
   302                  return (params: FormValues) => this.connectGitHubAppRepo(params as NewGitHubAppRepoParams);
   303              case ConnectionMethod.GOOGLECLOUD:
   304                  return (params: FormValues) => this.connectGoogleCloudSourceRepo(params as NewGoogleCloudSourceRepoParams);
   305          }
   306      }
   307  
   308      public render() {
   309          return (
   310              <Page
   311                  title='Repositories'
   312                  toolbar={{
   313                      breadcrumbs: [{title: 'Settings', path: '/settings'}, {title: 'Repositories'}],
   314                      actionMenu: {
   315                          items: [
   316                              {
   317                                  iconClassName: 'fa fa-plus',
   318                                  title: 'Connect Repo',
   319                                  action: () => (this.showConnectRepo = true)
   320                              },
   321                              {
   322                                  iconClassName: 'fa fa-redo',
   323                                  title: 'Refresh list',
   324                                  action: () => {
   325                                      this.refreshRepoList();
   326                                  }
   327                              }
   328                          ]
   329                      }
   330                  }}>
   331                  <div className='repos-list'>
   332                      <div className='argo-container'>
   333                          <div style={{display: 'flex', margin: '20px 0', justifyContent: 'space-between'}}>
   334                              <div style={{display: 'flex', gap: '8px', width: '50%'}}>
   335                                  <DropDownMenu
   336                                      items={[
   337                                          {
   338                                              title: 'all',
   339                                              action: () => this.setState({typeProperty: 'all'})
   340                                          },
   341                                          {
   342                                              title: 'git',
   343                                              action: () => this.setState({typeProperty: 'git'})
   344                                          },
   345                                          {
   346                                              title: 'helm',
   347                                              action: () => this.setState({typeProperty: 'helm'})
   348                                          }
   349                                      ]}
   350                                      anchor={() => (
   351                                          <>
   352                                              <a style={{whiteSpace: 'nowrap'}}>
   353                                                  Type: {this.state.typeProperty} <i className='fa fa-caret-down' />
   354                                              </a>
   355                                              &nbsp;
   356                                          </>
   357                                      )}
   358                                      qeId='type-menu'
   359                                  />
   360                                  <DataLoader load={services.repos.list} ref={loader => (this.repoLoader = loader)}>
   361                                      {(repos: models.Repository[]) => {
   362                                          const projectValues = Array.from(new Set(repos.map(repo => repo.project)));
   363  
   364                                          const projectItems = [
   365                                              {
   366                                                  title: 'all',
   367                                                  action: () => this.setState({projectProperty: 'all'})
   368                                              },
   369                                              ...projectValues
   370                                                  .filter(project => project && project.trim() !== '')
   371                                                  .map(project => ({
   372                                                      title: project,
   373                                                      action: () => this.setState({projectProperty: project})
   374                                                  }))
   375                                          ];
   376  
   377                                          return (
   378                                              <DropDownMenu
   379                                                  items={projectItems}
   380                                                  anchor={() => (
   381                                                      <>
   382                                                          <a style={{whiteSpace: 'nowrap'}}>
   383                                                              Project: {this.state.projectProperty} <i className='fa fa-caret-down' />
   384                                                          </a>
   385                                                          &nbsp;
   386                                                      </>
   387                                                  )}
   388                                                  qeId='project-menu'
   389                                              />
   390                                          );
   391                                      }}
   392                                  </DataLoader>
   393                                  <DropDownMenu
   394                                      items={[
   395                                          {
   396                                              title: 'all',
   397                                              action: () => this.setState({statusProperty: 'all'})
   398                                          },
   399                                          {
   400                                              title: 'Successful',
   401                                              action: () => this.setState({statusProperty: 'Successful'})
   402                                          },
   403                                          {
   404                                              title: 'Failed',
   405                                              action: () => this.setState({statusProperty: 'Failed'})
   406                                          },
   407                                          {
   408                                              title: 'Unknown',
   409                                              action: () => this.setState({statusProperty: 'Unknown'})
   410                                          }
   411                                      ]}
   412                                      anchor={() => (
   413                                          <>
   414                                              <a style={{whiteSpace: 'nowrap'}}>
   415                                                  Status: {this.state.statusProperty} <i className='fa fa-caret-down' />
   416                                              </a>
   417                                              &nbsp;
   418                                          </>
   419                                      )}
   420                                      qeId='status-menu'
   421                                  />
   422                              </div>
   423                              <div className='search-bar' style={{display: 'flex', alignItems: 'flex-end', width: '100%'}}></div>
   424                              <input type='text' className='argo-field' placeholder='Search Name' value={this.state.name} onChange={e => this.setState({name: e.target.value})} />
   425                          </div>
   426                          <DataLoader load={services.repos.list} ref={loader => (this.repoLoader = loader)}>
   427                              {(repos: models.Repository[]) => {
   428                                  const filteredRepos = this.filteredRepos(repos, this.state.typeProperty, this.state.projectProperty, this.state.statusProperty, this.state.name);
   429  
   430                                  return (
   431                                      (filteredRepos.length > 0 && (
   432                                          <div className='argo-table-list'>
   433                                              <div className='argo-table-list__head'>
   434                                                  <div className='row'>
   435                                                      <div className='columns small-1' />
   436                                                      <div className='columns small-1'>TYPE</div>
   437                                                      <div className='columns small-2'>NAME</div>
   438                                                      <div className='columns small-2'>PROJECT</div>
   439                                                      <div className='columns small-4'>REPOSITORY</div>
   440                                                      <div className='columns small-2'>CONNECTION STATUS</div>
   441                                                  </div>
   442                                              </div>
   443                                              {filteredRepos.map(repo => (
   444                                                  <div
   445                                                      className={`argo-table-list__row ${this.isRepoUpdatable(repo) ? 'item-clickable' : ''}`}
   446                                                      key={repo.repo}
   447                                                      onClick={() => (this.isRepoUpdatable(repo) ? this.displayEditSliding(repo) : null)}>
   448                                                      <div className='row'>
   449                                                          <div className='columns small-1'>
   450                                                              <i className={'icon argo-icon-' + (repo.type || 'git')} />
   451                                                          </div>
   452                                                          <div className='columns small-1'>
   453                                                              <span>{repo.type || 'git'}</span>
   454                                                              {repo.enableOCI && <span> OCI</span>}
   455                                                          </div>
   456                                                          <div className='columns small-2'>
   457                                                              <Tooltip content={repo.name}>
   458                                                                  <span>{repo.name}</span>
   459                                                              </Tooltip>
   460                                                          </div>
   461                                                          <div className='columns small-2'>
   462                                                              <Tooltip content={repo.project}>
   463                                                                  <span>{repo.project}</span>
   464                                                              </Tooltip>
   465                                                          </div>
   466                                                          <div className='columns small-4'>
   467                                                              <Tooltip content={repo.repo}>
   468                                                                  <span>
   469                                                                      <Repo url={repo.repo} />
   470                                                                  </span>
   471                                                              </Tooltip>
   472                                                          </div>
   473                                                          <div className='columns small-2'>
   474                                                              <ConnectionStateIcon state={repo.connectionState} /> {repo.connectionState.status}
   475                                                              <DropDownMenu
   476                                                                  anchor={() => (
   477                                                                      <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
   478                                                                          <i className='fa fa-ellipsis-v' />
   479                                                                      </button>
   480                                                                  )}
   481                                                                  items={[
   482                                                                      {
   483                                                                          title: 'Create application',
   484                                                                          action: () =>
   485                                                                              this.appContext.apis.navigation.goto('/applications', {
   486                                                                                  new: JSON.stringify({spec: {source: {repoURL: repo.repo}}})
   487                                                                              })
   488                                                                      },
   489                                                                      {
   490                                                                          title: 'Disconnect',
   491                                                                          action: () => this.disconnectRepo(repo.repo, repo.project, false)
   492                                                                      }
   493                                                                  ]}
   494                                                              />
   495                                                          </div>
   496                                                      </div>
   497                                                  </div>
   498                                              ))}
   499                                          </div>
   500                                      )) || (
   501                                          <EmptyState icon='argo-icon-git'>
   502                                              <h4>No repositories connected</h4>
   503                                              <h5>Connect your repo to deploy apps.</h5>
   504                                          </EmptyState>
   505                                      )
   506                                  );
   507                              }}
   508                          </DataLoader>
   509                      </div>
   510                      <div className='argo-container'>
   511                          <DataLoader load={() => services.repocreds.list()} ref={loader => (this.credsLoader = loader)}>
   512                              {(creds: models.RepoCreds[]) =>
   513                                  creds.length > 0 && (
   514                                      <div className='argo-table-list'>
   515                                          <div className='argo-table-list__head'>
   516                                              <div className='row'>
   517                                                  <div className='columns small-9'>CREDENTIALS TEMPLATE URL</div>
   518                                                  <div className='columns small-3'>CREDS</div>
   519                                              </div>
   520                                          </div>
   521                                          {creds.map(repo => (
   522                                              <div className='argo-table-list__row' key={repo.url}>
   523                                                  <div className='row'>
   524                                                      <div className='columns small-9'>
   525                                                          <i className='icon argo-icon-git' /> <Repo url={repo.url} />
   526                                                      </div>
   527                                                      <div className='columns small-3'>
   528                                                          -
   529                                                          <DropDownMenu
   530                                                              anchor={() => (
   531                                                                  <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
   532                                                                      <i className='fa fa-ellipsis-v' />
   533                                                                  </button>
   534                                                              )}
   535                                                              items={[
   536                                                                  {
   537                                                                      title: 'Remove',
   538                                                                      action: () => this.removeRepoCreds(repo.url, false)
   539                                                                  }
   540                                                              ]}
   541                                                          />
   542                                                      </div>
   543                                                  </div>
   544                                              </div>
   545                                          ))}
   546                                      </div>
   547                                  )
   548                              }
   549                          </DataLoader>
   550                      </div>
   551                      {this.state.authSettings?.hydratorEnabled && (
   552                          <div className='argo-container'>
   553                              <DataLoader load={() => services.repos.listWrite()} ref={loader => (this.repoLoader = loader)}>
   554                                  {(repos: models.Repository[]) =>
   555                                      (repos.length > 0 && (
   556                                          <div className='argo-table-list'>
   557                                              <div className='argo-table-list__head'>
   558                                                  <div className='row'>
   559                                                      <div className='columns small-1' />
   560                                                      <div className='columns small-1'>TYPE</div>
   561                                                      <div className='columns small-2'>NAME</div>
   562                                                      <div className='columns small-2'>PROJECT</div>
   563                                                      <div className='columns small-4'>REPOSITORY</div>
   564                                                      <div className='columns small-2'>CONNECTION STATUS</div>
   565                                                  </div>
   566                                              </div>
   567                                              {repos.map(repo => (
   568                                                  <div
   569                                                      className={`argo-table-list__row ${this.isRepoUpdatable(repo) ? 'item-clickable' : ''}`}
   570                                                      key={repo.repo}
   571                                                      onClick={() => (this.isRepoUpdatable(repo) ? this.displayEditSliding(repo) : null)}>
   572                                                      <div className='row'>
   573                                                          <div className='columns small-1'>
   574                                                              <i className='icon argo-icon-git' />
   575                                                          </div>
   576                                                          <div className='columns small-1'>write</div>
   577                                                          <div className='columns small-2'>
   578                                                              <Tooltip content={repo.name}>
   579                                                                  <span>{repo.name}</span>
   580                                                              </Tooltip>
   581                                                          </div>
   582                                                          <div className='columns small-2'>
   583                                                              <Tooltip content={repo.project}>
   584                                                                  <span>{repo.project}</span>
   585                                                              </Tooltip>
   586                                                          </div>
   587                                                          <div className='columns small-4'>
   588                                                              <Tooltip content={repo.repo}>
   589                                                                  <span>
   590                                                                      <Repo url={repo.repo} />
   591                                                                  </span>
   592                                                              </Tooltip>
   593                                                          </div>
   594                                                          <div className='columns small-2'>
   595                                                              <ConnectionStateIcon state={repo.connectionState} /> {repo.connectionState.status}
   596                                                              <DropDownMenu
   597                                                                  anchor={() => (
   598                                                                      <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
   599                                                                          <i className='fa fa-ellipsis-v' />
   600                                                                      </button>
   601                                                                  )}
   602                                                                  items={[
   603                                                                      {
   604                                                                          title: 'Create application',
   605                                                                          action: () =>
   606                                                                              this.appContext.apis.navigation.goto('/applications', {
   607                                                                                  new: JSON.stringify({spec: {sourceHydrator: {drySource: {repoURL: repo.repo}}}})
   608                                                                              })
   609                                                                      },
   610                                                                      {
   611                                                                          title: 'Disconnect',
   612                                                                          action: () => this.disconnectRepo(repo.repo, repo.project, true)
   613                                                                      }
   614                                                                  ]}
   615                                                              />
   616                                                          </div>
   617                                                      </div>
   618                                                  </div>
   619                                              ))}
   620                                          </div>
   621                                      )) || (
   622                                          <EmptyState icon='argo-icon-git'>
   623                                              <h4>No repositories connected</h4>
   624                                              <h5>Connect your repo to deploy apps.</h5>
   625                                          </EmptyState>
   626                                      )
   627                                  }
   628                              </DataLoader>
   629                          </div>
   630                      )}
   631                      {this.state.authSettings?.hydratorEnabled && (
   632                          <div className='argo-container'>
   633                              <DataLoader load={() => services.repocreds.listWrite()} ref={loader => (this.credsLoader = loader)}>
   634                                  {(creds: models.RepoCreds[]) =>
   635                                      creds.length > 0 && (
   636                                          <div className='argo-table-list'>
   637                                              <div className='argo-table-list__head'>
   638                                                  <div className='row'>
   639                                                      <div className='columns small-9'>CREDENTIALS TEMPLATE URL</div>
   640                                                      <div className='columns small-3'>CREDS</div>
   641                                                  </div>
   642                                              </div>
   643                                              {creds.map(repo => (
   644                                                  <div className='argo-table-list__row' key={repo.url}>
   645                                                      <div className='row'>
   646                                                          <div className='columns small-9'>
   647                                                              <i className='icon argo-icon-git' /> <Repo url={repo.url} />
   648                                                          </div>
   649                                                          <div className='columns small-3'>
   650                                                              -
   651                                                              <DropDownMenu
   652                                                                  anchor={() => (
   653                                                                      <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
   654                                                                          <i className='fa fa-ellipsis-v' />
   655                                                                      </button>
   656                                                                  )}
   657                                                                  items={[
   658                                                                      {
   659                                                                          title: 'Remove',
   660                                                                          action: () => this.removeRepoCreds(repo.url, true)
   661                                                                      }
   662                                                                  ]}
   663                                                              />
   664                                                          </div>
   665                                                      </div>
   666                                                  </div>
   667                                              ))}
   668                                          </div>
   669                                      )
   670                                  }
   671                              </DataLoader>
   672                          </div>
   673                      )}
   674                  </div>
   675                  <SlidingPanel
   676                      isShown={this.showConnectRepo || this.state.displayEditPanel}
   677                      onClose={() => {
   678                          if (!this.state.displayEditPanel && this.showConnectRepo) {
   679                              this.showConnectRepo = false;
   680                          }
   681                          if (this.state.displayEditPanel) {
   682                              this.setState({displayEditPanel: false});
   683                          }
   684                      }}
   685                      header={this.SlidingPanelHeader()}>
   686                      {this.showConnectRepo &&
   687                          this.ConnectRepoFormButton(this.state.method, method => {
   688                              this.setState({method});
   689                          })}
   690                      {this.state.displayEditPanel && <RepoDetails repo={this.state.currentRepo} save={(params: NewHTTPSRepoParams) => this.updateHTTPSRepo(params)} />}
   691                      {!this.state.displayEditPanel && (
   692                          <DataLoader load={() => services.projects.list('items.metadata.name').then(projects => projects.map(proj => proj.metadata.name).sort())}>
   693                              {projects => (
   694                                  <Form
   695                                      onSubmit={this.onSubmitForm()}
   696                                      getApi={api => (this.formApi = api)}
   697                                      defaultValues={this.onChooseDefaultValues()}
   698                                      validateError={(values: FormValues) => this.onValidateErrors(values)}>
   699                                      {formApi => (
   700                                          <form onSubmit={formApi.submitForm} role='form' className='repos-list width-control'>
   701                                              {this.state.authSettings?.hydratorEnabled && (
   702                                                  <div className='white-box'>
   703                                                      <p>SAVE AS WRITE CREDENTIAL (ALPHA)</p>
   704                                                      <p>
   705                                                          The Source Hydrator is an Alpha feature which enables Applications to push hydrated manifests to git before syncing. To use
   706                                                          the Source Hydrator for a repository, you must save two credentials: a read credential for pulling manifests and a write
   707                                                          credential for pushing hydrated manifests. If you add a write credential for a repository, then{' '}
   708                                                          <strong>any Application that can sync from the repo can also push hydrated manifests to that repo.</strong> Do not use this
   709                                                          feature until you've read its documentation and understand the security implications.
   710                                                      </p>
   711                                                      <div className='argo-form-row'>
   712                                                          <FormField formApi={formApi} label='Save as write credential' field='write' component={CheckboxField} />
   713                                                      </div>
   714                                                  </div>
   715                                              )}
   716                                              {this.state.method === ConnectionMethod.SSH && (
   717                                                  <div className='white-box'>
   718                                                      <p>CONNECT REPO USING SSH</p>
   719                                                      {formApi.getFormState().values.write === false && (
   720                                                          <div className='argo-form-row'>
   721                                                              <FormField formApi={formApi} label='Name (mandatory for Helm)' field='name' component={Text} />
   722                                                          </div>
   723                                                      )}
   724                                                      {formApi.getFormState().values.write === false && (
   725                                                          <div className='argo-form-row'>
   726                                                              <FormField
   727                                                                  formApi={formApi}
   728                                                                  label='Project'
   729                                                                  field='project'
   730                                                                  component={AutocompleteField}
   731                                                                  componentProps={{items: projects}}
   732                                                              />
   733                                                          </div>
   734                                                      )}
   735                                                      <div className='argo-form-row'>
   736                                                          <FormField formApi={formApi} label='Repository URL' field='url' component={Text} />
   737                                                      </div>
   738                                                      <div className='argo-form-row'>
   739                                                          <FormField formApi={formApi} label='SSH private key data' field='sshPrivateKey' component={TextArea} />
   740                                                      </div>
   741                                                      <div className='argo-form-row'>
   742                                                          <FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} />
   743                                                          <HelpIcon title='This setting is ignored when creating as credential template.' />
   744                                                      </div>
   745                                                      {formApi.getFormState().values.write === false && (
   746                                                          <div className='argo-form-row'>
   747                                                              <FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} />
   748                                                              <HelpIcon title='This setting is ignored when creating as credential template.' />
   749                                                          </div>
   750                                                      )}
   751                                                      <div className='argo-form-row'>
   752                                                          <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} />
   753                                                      </div>
   754                                                      <div className='argo-form-row'>
   755                                                          <FormField formApi={formApi} label='NoProxy (optional)' field='noProxy' component={Text} />
   756                                                      </div>
   757                                                  </div>
   758                                              )}
   759                                              {this.state.method === ConnectionMethod.HTTPS && (
   760                                                  <div className='white-box'>
   761                                                      <p>CONNECT REPO USING HTTP/HTTPS</p>
   762                                                      <div className='argo-form-row'>
   763                                                          <FormField
   764                                                              formApi={formApi}
   765                                                              label='Type'
   766                                                              field='type'
   767                                                              component={FormSelect}
   768                                                              componentProps={{options: ['git', 'helm', 'oci']}}
   769                                                          />
   770                                                      </div>
   771                                                      {(formApi.getFormState().values.type === 'helm' || formApi.getFormState().values.type === 'git') && (
   772                                                          <div className='argo-form-row'>
   773                                                              <FormField
   774                                                                  formApi={formApi}
   775                                                                  label={`Name ${formApi.getFormState().values.type === 'git' ? '(optional)' : ''}`}
   776                                                                  field='name'
   777                                                                  component={Text}
   778                                                              />
   779                                                          </div>
   780                                                      )}
   781                                                      {formApi.getFormState().values.write === false && (
   782                                                          <div className='argo-form-row'>
   783                                                              <FormField
   784                                                                  formApi={formApi}
   785                                                                  label='Project'
   786                                                                  field='project'
   787                                                                  component={AutocompleteField}
   788                                                                  componentProps={{items: projects}}
   789                                                              />
   790                                                          </div>
   791                                                      )}
   792                                                      <div className='argo-form-row'>
   793                                                          <FormField formApi={formApi} label='Repository URL' field='url' component={Text} />
   794                                                      </div>
   795                                                      <div className='argo-form-row'>
   796                                                          <FormField formApi={formApi} label='Username (optional)' field='username' component={Text} />
   797                                                      </div>
   798                                                      <div className='argo-form-row'>
   799                                                          <FormField
   800                                                              formApi={formApi}
   801                                                              label='Password (optional)'
   802                                                              field='password'
   803                                                              component={Text}
   804                                                              componentProps={{type: 'password'}}
   805                                                          />
   806                                                      </div>
   807                                                      {formApi.getFormState().values.type === 'git' && (
   808                                                          <div className='argo-form-row'>
   809                                                              <FormField
   810                                                                  formApi={formApi}
   811                                                                  label='Bearer token (optional, for BitBucket Data Center only)'
   812                                                                  field='bearerToken'
   813                                                                  component={Text}
   814                                                                  componentProps={{type: 'password'}}
   815                                                              />
   816                                                          </div>
   817                                                      )}
   818                                                      <div className='argo-form-row'>
   819                                                          <FormField formApi={formApi} label='TLS client certificate (optional)' field='tlsClientCertData' component={TextArea} />
   820                                                      </div>
   821                                                      <div className='argo-form-row'>
   822                                                          <FormField formApi={formApi} label='TLS client certificate key (optional)' field='tlsClientCertKey' component={TextArea} />
   823                                                      </div>
   824                                                      <div className='argo-form-row'>
   825                                                          <FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} />
   826                                                          <HelpIcon title='This setting is ignored when creating as credential template.' />
   827                                                      </div>
   828                                                      {formApi.getFormState().values.type === 'git' && (
   829                                                          <React.Fragment>
   830                                                              <div className='argo-form-row'>
   831                                                                  <FormField formApi={formApi} label='Force HTTP basic auth' field='forceHttpBasicAuth' component={CheckboxField} />
   832                                                              </div>
   833                                                              <div className='argo-form-row'>
   834                                                                  <FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} />
   835                                                                  <HelpIcon title='This setting is ignored when creating as credential template.' />
   836                                                              </div>
   837                                                          </React.Fragment>
   838                                                      )}
   839                                                      <div className='argo-form-row'>
   840                                                          <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} />
   841                                                      </div>
   842                                                      <div className='argo-form-row'>
   843                                                          <FormField formApi={formApi} label='NoProxy (optional)' field='noProxy' component={Text} />
   844                                                      </div>
   845                                                      <div className='argo-form-row'>
   846                                                          {formApi.getFormState().values.type !== 'oci' ? (
   847                                                              <FormField formApi={formApi} label='Enable OCI' field='enableOCI' component={CheckboxField} />
   848                                                          ) : (
   849                                                              <FormField formApi={formApi} label='Insecure HTTP Only' field='insecureOCIForceHttp' component={CheckboxField} />
   850                                                          )}
   851                                                      </div>
   852                                                      <div className='argo-form-row'>
   853                                                          <FormField
   854                                                              formApi={formApi}
   855                                                              label='Use Azure Workload Identity'
   856                                                              field='useAzureWorkloadIdentity'
   857                                                              component={CheckboxField}
   858                                                          />
   859                                                      </div>
   860                                                  </div>
   861                                              )}
   862                                              {this.state.method === ConnectionMethod.GITHUBAPP && (
   863                                                  <div className='white-box'>
   864                                                      <p>CONNECT REPO USING GITHUB APP</p>
   865                                                      <div className='argo-form-row'>
   866                                                          <FormField
   867                                                              formApi={formApi}
   868                                                              label='Type'
   869                                                              field='ghType'
   870                                                              component={FormSelect}
   871                                                              componentProps={{options: ['GitHub', 'GitHub Enterprise']}}
   872                                                          />
   873                                                      </div>
   874                                                      {formApi.getFormState().values.ghType === 'GitHub Enterprise' && (
   875                                                          <div className='argo-form-row'>
   876                                                              <FormField
   877                                                                  formApi={formApi}
   878                                                                  label='GitHub Enterprise Base URL (e.g. https://ghe.example.com/api/v3)'
   879                                                                  field='githubAppEnterpriseBaseURL'
   880                                                                  component={Text}
   881                                                              />
   882                                                          </div>
   883                                                      )}
   884                                                      <div className='argo-form-row'>
   885                                                          <FormField
   886                                                              formApi={formApi}
   887                                                              label='Project'
   888                                                              field='project'
   889                                                              component={AutocompleteField}
   890                                                              componentProps={{items: projects}}
   891                                                          />
   892                                                      </div>
   893                                                      <div className='argo-form-row'>
   894                                                          <FormField formApi={formApi} label='Repository URL' field='url' component={Text} />
   895                                                      </div>
   896                                                      <div className='argo-form-row'>
   897                                                          <FormField formApi={formApi} label='GitHub App ID' field='githubAppId' component={NumberField} />
   898                                                      </div>
   899                                                      <div className='argo-form-row'>
   900                                                          <FormField formApi={formApi} label='GitHub App Installation ID' field='githubAppInstallationId' component={NumberField} />
   901                                                      </div>
   902                                                      <div className='argo-form-row'>
   903                                                          <FormField formApi={formApi} label='GitHub App private key' field='githubAppPrivateKey' component={TextArea} />
   904                                                      </div>
   905                                                      <div className='argo-form-row'>
   906                                                          <FormField formApi={formApi} label='Skip server verification' field='insecure' component={CheckboxField} />
   907                                                          <HelpIcon title='This setting is ignored when creating as credential template.' />
   908                                                      </div>
   909                                                      <div className='argo-form-row'>
   910                                                          <FormField formApi={formApi} label='Enable LFS support (Git only)' field='enableLfs' component={CheckboxField} />
   911                                                          <HelpIcon title='This setting is ignored when creating as credential template.' />
   912                                                      </div>
   913                                                      {formApi.getFormState().values.ghType === 'GitHub Enterprise' && (
   914                                                          <React.Fragment>
   915                                                              <div className='argo-form-row'>
   916                                                                  <FormField
   917                                                                      formApi={formApi}
   918                                                                      label='TLS client certificate (optional)'
   919                                                                      field='tlsClientCertData'
   920                                                                      component={TextArea}
   921                                                                  />
   922                                                              </div>
   923                                                              <div className='argo-form-row'>
   924                                                                  <FormField
   925                                                                      formApi={formApi}
   926                                                                      label='TLS client certificate key (optional)'
   927                                                                      field='tlsClientCertKey'
   928                                                                      component={TextArea}
   929                                                                  />
   930                                                              </div>
   931                                                          </React.Fragment>
   932                                                      )}
   933                                                      <div className='argo-form-row'>
   934                                                          <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} />
   935                                                      </div>
   936                                                      <div className='argo-form-row'>
   937                                                          <FormField formApi={formApi} label='NoProxy (optional)' field='noProxy' component={Text} />
   938                                                      </div>
   939                                                  </div>
   940                                              )}
   941                                              {this.state.method === ConnectionMethod.GOOGLECLOUD && (
   942                                                  <div className='white-box'>
   943                                                      <p>CONNECT REPO USING GOOGLE CLOUD</p>
   944                                                      <div className='argo-form-row'>
   945                                                          <FormField
   946                                                              formApi={formApi}
   947                                                              label='Project'
   948                                                              field='project'
   949                                                              component={AutocompleteField}
   950                                                              componentProps={{items: projects}}
   951                                                          />
   952                                                      </div>
   953                                                      <div className='argo-form-row'>
   954                                                          <FormField formApi={formApi} label='Repository URL' field='url' component={Text} />
   955                                                      </div>
   956                                                      <div className='argo-form-row'>
   957                                                          <FormField formApi={formApi} label='GCP service account key' field='gcpServiceAccountKey' component={TextArea} />
   958                                                      </div>
   959                                                      <div className='argo-form-row'>
   960                                                          <FormField formApi={formApi} label='Proxy (optional)' field='proxy' component={Text} />
   961                                                      </div>
   962                                                      <div className='argo-form-row'>
   963                                                          <FormField formApi={formApi} label='NoProxy (optional)' field='noProxy' component={Text} />
   964                                                      </div>
   965                                                  </div>
   966                                              )}
   967                                          </form>
   968                                      )}
   969                                  </Form>
   970                              )}
   971                          </DataLoader>
   972                      )}
   973                  </SlidingPanel>
   974              </Page>
   975          );
   976      }
   977  
   978      private displayEditSliding(repo: models.Repository) {
   979          this.setState({currentRepo: repo});
   980          this.setState({displayEditPanel: true});
   981      }
   982  
   983      // Whether url is a http or https url
   984      private isHTTPOrHTTPSUrl(url: string) {
   985          if (url.match(/^https?:\/\/.*$/gi)) {
   986              return true;
   987          } else {
   988              return false;
   989          }
   990      }
   991  
   992      // Whether url is an oci url (simple version)
   993      private isOCIUrl(url: string) {
   994          if (url.match(/^oci:\/\/.*$/gi)) {
   995              return true;
   996          } else {
   997              return false;
   998          }
   999      }
  1000  
  1001      private stripProtocol(url: string) {
  1002          return url.replace('https://', '').replace('oci://', '');
  1003      }
  1004  
  1005      // only connections of git type which is not via GitHub App are updatable
  1006      private isRepoUpdatable(repo: models.Repository) {
  1007          return this.isHTTPOrHTTPSUrl(repo.repo) && repo.type === 'git' && !repo.githubAppId;
  1008      }
  1009  
  1010      // Forces a reload of configured repositories, circumventing the cache
  1011      private async refreshRepoList(updatedRepo?: string) {
  1012          // Refresh the credentials template list
  1013          this.credsLoader.reload();
  1014  
  1015          try {
  1016              await services.repos.listNoCache();
  1017              this.repoLoader.reload();
  1018              this.appContext.apis.notifications.show({
  1019                  content: updatedRepo ? `Successfully updated ${updatedRepo} repository` : 'Successfully reloaded list of repositories',
  1020                  type: NotificationType.Success
  1021              });
  1022          } catch (e) {
  1023              this.appContext.apis.notifications.show({
  1024                  content: <ErrorNotification title='Could not refresh list of repositories' e={e} />,
  1025                  type: NotificationType.Error
  1026              });
  1027          }
  1028      }
  1029  
  1030      // Empty all fields in connect repository form
  1031      private clearConnectRepoForm() {
  1032          this.credsTemplate = false;
  1033          this.formApi.resetAll();
  1034      }
  1035  
  1036      // Connect a new repository or create a repository credentials for SSH repositories
  1037      private async connectSSHRepo(params: NewSSHRepoParams) {
  1038          if (this.credsTemplate) {
  1039              this.createSSHCreds({url: params.url, sshPrivateKey: params.sshPrivateKey, write: params.write});
  1040          } else {
  1041              this.setState({connecting: true});
  1042              try {
  1043                  if (params.write) {
  1044                      await services.repos.createSSHWrite(params);
  1045                  } else {
  1046                      await services.repos.createSSH(params);
  1047                  }
  1048                  this.repoLoader.reload();
  1049                  this.showConnectRepo = false;
  1050              } catch (e) {
  1051                  this.appContext.apis.notifications.show({
  1052                      content: <ErrorNotification title='Unable to connect SSH repository' e={e} />,
  1053                      type: NotificationType.Error
  1054                  });
  1055              } finally {
  1056                  this.setState({connecting: false});
  1057              }
  1058          }
  1059      }
  1060  
  1061      // Connect a new repository or create a repository credentials for HTTPS repositories
  1062      private async connectHTTPSRepo(params: NewHTTPSRepoParams) {
  1063          if (this.credsTemplate) {
  1064              await this.createHTTPSCreds({
  1065                  type: params.type,
  1066                  url: params.url,
  1067                  username: params.username,
  1068                  password: params.password,
  1069                  bearerToken: params.bearerToken,
  1070                  tlsClientCertData: params.tlsClientCertData,
  1071                  tlsClientCertKey: params.tlsClientCertKey,
  1072                  proxy: params.proxy,
  1073                  noProxy: params.noProxy,
  1074                  forceHttpBasicAuth: params.forceHttpBasicAuth,
  1075                  enableOCI: params.enableOCI,
  1076                  write: params.write,
  1077                  useAzureWorkloadIdentity: params.useAzureWorkloadIdentity,
  1078                  insecureOCIForceHttp: params.insecureOCIForceHttp
  1079              });
  1080          } else {
  1081              this.setState({connecting: true});
  1082              try {
  1083                  if (params.write) {
  1084                      await services.repos.createHTTPSWrite(params);
  1085                  } else {
  1086                      await services.repos.createHTTPS(params);
  1087                  }
  1088                  this.repoLoader.reload();
  1089                  this.showConnectRepo = false;
  1090              } catch (e) {
  1091                  this.appContext.apis.notifications.show({
  1092                      content: <ErrorNotification title='Unable to connect HTTPS repository' e={e} />,
  1093                      type: NotificationType.Error
  1094                  });
  1095              } finally {
  1096                  this.setState({connecting: false});
  1097              }
  1098          }
  1099      }
  1100  
  1101      // Update an existing repository for HTTPS repositories
  1102      private async updateHTTPSRepo(params: NewHTTPSRepoParams) {
  1103          try {
  1104              if (params.write) {
  1105                  await services.repos.updateHTTPSWrite(params);
  1106              } else {
  1107                  await services.repos.updateHTTPS(params);
  1108              }
  1109              this.repoLoader.reload();
  1110              this.setState({displayEditPanel: false});
  1111              this.refreshRepoList(params.url);
  1112          } catch (e) {
  1113              this.appContext.apis.notifications.show({
  1114                  content: <ErrorNotification title='Unable to update HTTPS repository' e={e} />,
  1115                  type: NotificationType.Error
  1116              });
  1117          } finally {
  1118              this.setState({connecting: false});
  1119          }
  1120      }
  1121  
  1122      // Connect a new repository or create a repository credentials for GitHub App repositories
  1123      private async connectGitHubAppRepo(params: NewGitHubAppRepoParams) {
  1124          if (this.credsTemplate) {
  1125              this.createGitHubAppCreds({
  1126                  url: params.url,
  1127                  githubAppPrivateKey: params.githubAppPrivateKey,
  1128                  githubAppId: params.githubAppId,
  1129                  githubAppInstallationId: params.githubAppInstallationId,
  1130                  githubAppEnterpriseBaseURL: params.githubAppEnterpriseBaseURL,
  1131                  tlsClientCertData: params.tlsClientCertData,
  1132                  tlsClientCertKey: params.tlsClientCertKey,
  1133                  proxy: params.proxy,
  1134                  noProxy: params.noProxy,
  1135                  write: params.write
  1136              });
  1137          } else {
  1138              this.setState({connecting: true});
  1139              try {
  1140                  if (params.write) {
  1141                      await services.repos.createGitHubAppWrite(params);
  1142                  } else {
  1143                      await services.repos.createGitHubApp(params);
  1144                  }
  1145                  this.repoLoader.reload();
  1146                  this.showConnectRepo = false;
  1147              } catch (e) {
  1148                  this.appContext.apis.notifications.show({
  1149                      content: <ErrorNotification title='Unable to connect GitHub App repository' e={e} />,
  1150                      type: NotificationType.Error
  1151                  });
  1152              } finally {
  1153                  this.setState({connecting: false});
  1154              }
  1155          }
  1156      }
  1157  
  1158      // Connect a new repository or create a repository credentials for GitHub App repositories
  1159      private async connectGoogleCloudSourceRepo(params: NewGoogleCloudSourceRepoParams) {
  1160          if (this.credsTemplate) {
  1161              this.createGoogleCloudSourceCreds({
  1162                  url: params.url,
  1163                  gcpServiceAccountKey: params.gcpServiceAccountKey,
  1164                  write: params.write
  1165              });
  1166          } else {
  1167              this.setState({connecting: true});
  1168              try {
  1169                  if (params.write) {
  1170                      await services.repos.createGoogleCloudSourceWrite(params);
  1171                  } else {
  1172                      await services.repos.createGoogleCloudSource(params);
  1173                  }
  1174                  this.repoLoader.reload();
  1175                  this.showConnectRepo = false;
  1176              } catch (e) {
  1177                  this.appContext.apis.notifications.show({
  1178                      content: <ErrorNotification title='Unable to connect Google Cloud Source repository' e={e} />,
  1179                      type: NotificationType.Error
  1180                  });
  1181              } finally {
  1182                  this.setState({connecting: false});
  1183              }
  1184          }
  1185      }
  1186  
  1187      private async createHTTPSCreds(params: NewHTTPSRepoCredsParams) {
  1188          try {
  1189              if (params.write) {
  1190                  await services.repocreds.createHTTPSWrite(params);
  1191              } else {
  1192                  await services.repocreds.createHTTPS(params);
  1193              }
  1194              this.credsLoader.reload();
  1195              this.showConnectRepo = false;
  1196          } catch (e) {
  1197              this.appContext.apis.notifications.show({
  1198                  content: <ErrorNotification title='Unable to create HTTPS credentials' e={e} />,
  1199                  type: NotificationType.Error
  1200              });
  1201          }
  1202      }
  1203  
  1204      private async createSSHCreds(params: NewSSHRepoCredsParams) {
  1205          try {
  1206              if (params.write) {
  1207                  await services.repocreds.createSSHWrite(params);
  1208              } else {
  1209                  await services.repocreds.createSSH(params);
  1210              }
  1211              this.credsLoader.reload();
  1212              this.showConnectRepo = false;
  1213          } catch (e) {
  1214              this.appContext.apis.notifications.show({
  1215                  content: <ErrorNotification title='Unable to create SSH credentials' e={e} />,
  1216                  type: NotificationType.Error
  1217              });
  1218          }
  1219      }
  1220  
  1221      private async createGitHubAppCreds(params: NewGitHubAppRepoCredsParams) {
  1222          try {
  1223              if (params.write) {
  1224                  await services.repocreds.createGitHubAppWrite(params);
  1225              } else {
  1226                  await services.repocreds.createGitHubApp(params);
  1227              }
  1228              this.credsLoader.reload();
  1229              this.showConnectRepo = false;
  1230          } catch (e) {
  1231              this.appContext.apis.notifications.show({
  1232                  content: <ErrorNotification title='Unable to create GitHub App credentials' e={e} />,
  1233                  type: NotificationType.Error
  1234              });
  1235          }
  1236      }
  1237  
  1238      private async createGoogleCloudSourceCreds(params: NewGoogleCloudSourceRepoCredsParams) {
  1239          try {
  1240              if (params.write) {
  1241                  await services.repocreds.createGoogleCloudSourceWrite(params);
  1242              } else {
  1243                  await services.repocreds.createGoogleCloudSource(params);
  1244              }
  1245              this.credsLoader.reload();
  1246              this.showConnectRepo = false;
  1247          } catch (e) {
  1248              this.appContext.apis.notifications.show({
  1249                  content: <ErrorNotification title='Unable to create Google Cloud Source credentials' e={e} />,
  1250                  type: NotificationType.Error
  1251              });
  1252          }
  1253      }
  1254  
  1255      // Remove a repository from the configuration
  1256      private async disconnectRepo(repo: string, project: string, write: boolean) {
  1257          const confirmed = await this.appContext.apis.popup.confirm('Disconnect repository', `Are you sure you want to disconnect '${repo}'?`);
  1258          if (confirmed) {
  1259              try {
  1260                  if (write) {
  1261                      await services.repos.deleteWrite(repo, project || '');
  1262                  } else {
  1263                      await services.repos.delete(repo, project || '');
  1264                  }
  1265                  this.repoLoader.reload();
  1266              } catch (e) {
  1267                  this.appContext.apis.notifications.show({
  1268                      content: <ErrorNotification title='Unable to disconnect repository' e={e} />,
  1269                      type: NotificationType.Error
  1270                  });
  1271              }
  1272          }
  1273      }
  1274  
  1275      // Remove repository credentials from the configuration
  1276      private async removeRepoCreds(url: string, write: boolean) {
  1277          const confirmed = await this.appContext.apis.popup.confirm('Remove repository credentials', `Are you sure you want to remove credentials for URL prefix '${url}'?`);
  1278          if (confirmed) {
  1279              try {
  1280                  if (write) {
  1281                      await services.repocreds.deleteWrite(url);
  1282                  } else {
  1283                      await services.repocreds.delete(url);
  1284                  }
  1285                  this.credsLoader.reload();
  1286              } catch (e) {
  1287                  this.appContext.apis.notifications.show({
  1288                      content: <ErrorNotification title='Unable to remove repository credentials' e={e} />,
  1289                      type: NotificationType.Error
  1290                  });
  1291              }
  1292          }
  1293      }
  1294  
  1295      // filtering function
  1296      private filteredRepos(repos: models.Repository[], type: string, project: string, status: string, name: string) {
  1297          let newRepos = repos;
  1298  
  1299          if (name && name.trim() !== '') {
  1300              const response = this.filteredName(newRepos, name);
  1301              newRepos = response;
  1302          }
  1303  
  1304          if (type !== 'all') {
  1305              const response = this.filteredType(newRepos, type);
  1306              newRepos = response;
  1307          }
  1308  
  1309          if (status !== 'all') {
  1310              const response = this.filteredStatus(newRepos, status);
  1311              newRepos = response;
  1312          }
  1313  
  1314          if (project !== 'all') {
  1315              const response = this.filteredProject(newRepos, project);
  1316              newRepos = response;
  1317          }
  1318  
  1319          return newRepos;
  1320      }
  1321  
  1322      private filteredName(repos: models.Repository[], name: string) {
  1323          const trimmedName = name.trim();
  1324          if (trimmedName === '') {
  1325              return repos;
  1326          }
  1327          const newRepos = repos.filter(
  1328              repo => (repo.name && repo.name.toLowerCase().includes(trimmedName.toLowerCase())) || repo.repo.toLowerCase().includes(trimmedName.toLowerCase())
  1329          );
  1330          return newRepos;
  1331      }
  1332  
  1333      private filteredStatus(repos: models.Repository[], status: string) {
  1334          const newRepos = repos.filter(repo => repo.connectionState.status.includes(status));
  1335          return newRepos;
  1336      }
  1337  
  1338      private filteredProject(repos: models.Repository[], project: string) {
  1339          const newRepos = repos.filter(repo => repo.project && repo.project.includes(project));
  1340          return newRepos;
  1341      }
  1342  
  1343      private filteredType(repos: models.Repository[], type: string) {
  1344          const newRepos = repos.filter(repo => repo.type.includes(type));
  1345          return newRepos;
  1346      }
  1347  
  1348      // Whether to show the new repository connection dialogue on the page
  1349      private get showConnectRepo() {
  1350          return new URLSearchParams(this.props.location.search).get('addRepo') === 'true';
  1351      }
  1352  
  1353      private set showConnectRepo(val: boolean) {
  1354          this.clearConnectRepoForm();
  1355          this.appContext.router.history.push(`${this.props.match.url}?addRepo=${val}`);
  1356      }
  1357  
  1358      private get appContext(): AppContext {
  1359          return this.context as AppContext;
  1360      }
  1361  }