github.com/argoproj/argo-cd@v1.8.7/ui/src/app/settings/components/certs-list/certs-list.tsx (about)

     1  import {DropDownMenu, FormField, NotificationType, SlidingPanel} from 'argo-ui';
     2  import * as PropTypes from 'prop-types';
     3  import * as React from 'react';
     4  import {Form, FormApi, Text, TextArea} from 'react-form';
     5  import {RouteComponentProps} from 'react-router';
     6  
     7  import {DataLoader, EmptyState, ErrorNotification, Page} from '../../../shared/components';
     8  import {AppContext} from '../../../shared/context';
     9  import * as models from '../../../shared/models';
    10  import {services} from '../../../shared/services';
    11  
    12  require('./certs-list.scss');
    13  
    14  interface NewTLSCertParams {
    15      serverName: string;
    16      certType: string;
    17      certData: string;
    18  }
    19  
    20  interface NewSSHKnownHostParams {
    21      certData: string;
    22  }
    23  
    24  export class CertsList extends React.Component<RouteComponentProps<any>> {
    25      public static contextTypes = {
    26          router: PropTypes.object,
    27          apis: PropTypes.object,
    28          history: PropTypes.object
    29      };
    30  
    31      private formApiTLS: FormApi;
    32      private formApiSSH: FormApi;
    33      private loader: DataLoader;
    34  
    35      public render() {
    36          return (
    37              <Page
    38                  title='Repository certificates'
    39                  toolbar={{
    40                      breadcrumbs: [{title: 'Settings', path: '/settings'}, {title: 'Repository certificates'}],
    41                      actionMenu: {
    42                          className: 'fa fa-plus',
    43                          items: [
    44                              {
    45                                  title: 'Add TLS certificate',
    46                                  action: () => (this.showAddTLSCertificate = true)
    47                              },
    48                              {
    49                                  title: 'Add SSH known hosts',
    50                                  action: () => (this.showAddSSHKnownHosts = true)
    51                              }
    52                          ]
    53                      }
    54                  }}>
    55                  <div className='certs-list'>
    56                      <div className='argo-container'>
    57                          <DataLoader load={() => services.certs.list()} ref={loader => (this.loader = loader)}>
    58                              {(certs: models.RepoCert[]) =>
    59                                  (certs.length > 0 && (
    60                                      <div className='argo-table-list'>
    61                                          <div className='argo-table-list__head'>
    62                                              <div className='row'>
    63                                                  <div className='columns small-3'>SERVER NAME</div>
    64                                                  <div className='columns small-3'>CERT TYPE</div>
    65                                                  <div className='columns small-6'>CERT INFO</div>
    66                                              </div>
    67                                          </div>
    68                                          {certs.map(cert => (
    69                                              <div className='argo-table-list__row' key={cert.certType + '_' + cert.certSubType + '_' + cert.serverName}>
    70                                                  <div className='row'>
    71                                                      <div className='columns small-3'>
    72                                                          <i className='icon argo-icon-git' /> {cert.serverName}
    73                                                      </div>
    74                                                      <div className='columns small-3'>
    75                                                          {cert.certType} {cert.certSubType}
    76                                                      </div>
    77                                                      <div className='columns small-6'>
    78                                                          {cert.certInfo}
    79                                                          <DropDownMenu
    80                                                              anchor={() => (
    81                                                                  <button className='argo-button argo-button--light argo-button--lg argo-button--short'>
    82                                                                      <i className='fa fa-ellipsis-v' />
    83                                                                  </button>
    84                                                              )}
    85                                                              items={[
    86                                                                  {
    87                                                                      title: 'Remove',
    88                                                                      action: () => this.removeCert(cert.serverName, cert.certType, cert.certSubType)
    89                                                                  }
    90                                                              ]}
    91                                                          />
    92                                                      </div>
    93                                                  </div>
    94                                              </div>
    95                                          ))}
    96                                      </div>
    97                                  )) || (
    98                                      <EmptyState icon='argo-icon-git'>
    99                                          <h4>No certificates configured</h4>
   100                                          <h5>You can add further certificates below..</h5>
   101                                          <button className='argo-button argo-button--base' onClick={() => (this.showAddTLSCertificate = true)}>
   102                                              Add TLS certificates
   103                                          </button>{' '}
   104                                          <button className='argo-button argo-button--base' onClick={() => (this.showAddSSHKnownHosts = true)}>
   105                                              Add SSH known hosts
   106                                          </button>
   107                                      </EmptyState>
   108                                  )
   109                              }
   110                          </DataLoader>
   111                      </div>
   112                  </div>
   113                  <SlidingPanel
   114                      isShown={this.showAddTLSCertificate}
   115                      onClose={() => (this.showAddTLSCertificate = false)}
   116                      header={
   117                          <div>
   118                              <button className='argo-button argo-button--base' onClick={() => this.formApiTLS.submitForm(null)}>
   119                                  Create
   120                              </button>{' '}
   121                              <button onClick={() => (this.showAddTLSCertificate = false)} className='argo-button argo-button--base-o'>
   122                                  Cancel
   123                              </button>
   124                          </div>
   125                      }>
   126                      <h4>Create TLS repository certificate</h4>
   127                      <Form
   128                          onSubmit={params => this.addTLSCertificate(params as NewTLSCertParams)}
   129                          getApi={api => (this.formApiTLS = api)}
   130                          preSubmit={(params: NewTLSCertParams) => ({
   131                              serverName: params.serverName,
   132                              certData: btoa(params.certData)
   133                          })}
   134                          validateError={(params: NewTLSCertParams) => ({
   135                              serverName: !params.serverName && 'Repository server name is required',
   136                              certData: !params.certData && 'Certificate data is required'
   137                          })}>
   138                          {formApiTLS => (
   139                              <form onSubmit={formApiTLS.submitForm} role='form' className='certs-list width-control' encType='multipart/form-data'>
   140                                  <div className='argo-form-row'>
   141                                      <FormField formApi={formApiTLS} label='Repository server name' field='serverName' component={Text} />
   142                                  </div>
   143                                  <div className='argo-form-row'>
   144                                      <FormField formApi={formApiTLS} label='TLS certificate (PEM format)' field='certData' component={TextArea} />
   145                                  </div>
   146                              </form>
   147                          )}
   148                      </Form>
   149                  </SlidingPanel>
   150                  <SlidingPanel
   151                      isShown={this.showAddSSHKnownHosts}
   152                      onClose={() => (this.showAddSSHKnownHosts = false)}
   153                      header={
   154                          <div>
   155                              <button className='argo-button argo-button--base' onClick={() => this.formApiSSH.submitForm(null)}>
   156                                  Create
   157                              </button>{' '}
   158                              <button onClick={() => (this.showAddSSHKnownHosts = false)} className='argo-button argo-button--base-o'>
   159                                  Cancel
   160                              </button>
   161                          </div>
   162                      }>
   163                      <h4>Create SSH known host entries</h4>
   164                      <p>
   165                          Paste SSH known hosts data in the text area below, one entry per line. You can use output from e.g. <code>ssh-keyscan</code> or the contents of an{' '}
   166                          <code>ssh_known_hosts</code> file in a verbatim way. Lines starting with <code>#</code> will be treated as comments and be ignored.
   167                      </p>
   168                      <p>
   169                          <strong>Make sure there are no linebreaks in the keys.</strong>
   170                      </p>
   171                      <Form
   172                          onSubmit={params => this.addSSHKnownHosts(params as NewSSHKnownHostParams)}
   173                          getApi={api => (this.formApiSSH = api)}
   174                          preSubmit={(params: NewSSHKnownHostParams) => ({
   175                              certData: btoa(params.certData)
   176                          })}
   177                          validateError={(params: NewSSHKnownHostParams) => ({
   178                              certData: !params.certData && 'SSH known hosts data is required'
   179                          })}>
   180                          {formApiSSH => (
   181                              <form onSubmit={formApiSSH.submitForm} role='form' className='certs-list width-control' encType='multipart/form-data'>
   182                                  <div className='argo-form-row'>
   183                                      <FormField formApi={formApiSSH} label='SSH known hosts data' field='certData' component={TextArea} />
   184                                  </div>
   185                              </form>
   186                          )}
   187                      </Form>
   188                  </SlidingPanel>
   189              </Page>
   190          );
   191      }
   192  
   193      private clearForms() {
   194          this.formApiSSH.resetAll();
   195          this.formApiTLS.resetAll();
   196      }
   197  
   198      private async addTLSCertificate(params: NewTLSCertParams) {
   199          try {
   200              await services.certs.create({items: [{serverName: params.serverName, certType: 'https', certData: params.certData, certSubType: '', certInfo: ''}], metadata: null});
   201              this.showAddTLSCertificate = false;
   202              this.loader.reload();
   203          } catch (e) {
   204              this.appContext.apis.notifications.show({
   205                  content: <ErrorNotification title='Unable to add TLS certificate' e={e} />,
   206                  type: NotificationType.Error
   207              });
   208          }
   209      }
   210  
   211      private async addSSHKnownHosts(params: NewSSHKnownHostParams) {
   212          try {
   213              let knownHostEntries: models.RepoCert[] = [];
   214              atob(params.certData)
   215                  .split('\n')
   216                  .forEach(function processEntry(item, index) {
   217                      const trimmedLine = item.trimLeft();
   218                      if (trimmedLine.startsWith('#') === false) {
   219                          const knownHosts = trimmedLine.split(' ', 3);
   220                          if (knownHosts.length === 3) {
   221                              // Perform a little sanity check on the data - server
   222                              // checks too, but let's not send it invalid data in
   223                              // the first place.
   224                              const subType = knownHosts[1].match(/^(ssh\-[a-z0-9]+|ecdsa-[a-z0-9\-]+)$/gi);
   225                              if (subType != null) {
   226                                  // Key could be valid for multiple hosts
   227                                  const hostnames = knownHosts[0].split(',');
   228                                  for (const hostname of hostnames) {
   229                                      knownHostEntries = knownHostEntries.concat({
   230                                          serverName: hostname,
   231                                          certType: 'ssh',
   232                                          certSubType: knownHosts[1],
   233                                          certData: btoa(knownHosts[2]),
   234                                          certInfo: ''
   235                                      });
   236                                  }
   237                              } else {
   238                                  throw new Error('Invalid SSH subtype: ' + subType);
   239                              }
   240                          }
   241                      }
   242                  });
   243              if (knownHostEntries.length === 0) {
   244                  throw new Error('No valid known hosts data entered');
   245              }
   246              await services.certs.create({items: knownHostEntries, metadata: null});
   247              this.showAddSSHKnownHosts = false;
   248              this.loader.reload();
   249          } catch (e) {
   250              this.appContext.apis.notifications.show({
   251                  content: <ErrorNotification title='Unable to add SSH known hosts data' e={e} />,
   252                  type: NotificationType.Error
   253              });
   254          }
   255      }
   256  
   257      private async removeCert(serverName: string, certType: string, certSubType: string) {
   258          const confirmed = await this.appContext.apis.popup.confirm('Remove certificate', 'Are you sure you want to remove ' + certType + ' certificate for ' + serverName + '?');
   259          if (confirmed) {
   260              await services.certs.delete(serverName, certType, certSubType);
   261              this.loader.reload();
   262          }
   263      }
   264  
   265      private get showAddTLSCertificate() {
   266          return new URLSearchParams(this.props.location.search).get('addTLSCert') === 'true';
   267      }
   268  
   269      private set showAddTLSCertificate(val: boolean) {
   270          this.clearForms();
   271          this.appContext.router.history.push(`${this.props.match.url}?addTLSCert=${val}`);
   272      }
   273  
   274      private get showAddSSHKnownHosts() {
   275          return new URLSearchParams(this.props.location.search).get('addSSHKnownHosts') === 'true';
   276      }
   277  
   278      private set showAddSSHKnownHosts(val: boolean) {
   279          this.clearForms();
   280          this.appContext.router.history.push(`${this.props.match.url}?addSSHKnownHosts=${val}`);
   281      }
   282  
   283      private get appContext(): AppContext {
   284          return this.context as AppContext;
   285      }
   286  }