github.com/argoproj/argo-cd/v3@v3.2.1/ui/src/app/settings/components/project-role-jwt-tokens/project-role-jwt-tokens.tsx (about)

     1  import {FormField, NotificationType, Tooltip} from 'argo-ui';
     2  import * as React from 'react';
     3  import {Form, FormApi, Text} from 'react-form';
     4  
     5  import {Consumer, ContextApis} from '../../../shared/context';
     6  import {JwtToken} from '../../../shared/models';
     7  import {CreateJWTTokenParams, DeleteJWTTokenParams} from '../../../shared/services';
     8  import {convertExpiresInToSeconds, validExpiresIn} from '../utils';
     9  import {Button} from '../../../shared/components/button';
    10  import {ProgressPopup} from '../../../shared/components';
    11  
    12  interface ProjectRoleJWTTokensProps {
    13      projName: string;
    14      roleName: string;
    15      tokens: JwtToken[];
    16      token: string;
    17      createJWTToken: (params: CreateJWTTokenParams) => Promise<void>;
    18      deleteJWTToken: (params: DeleteJWTTokenParams) => Promise<void>;
    19      hideJWTToken: () => void;
    20      getApi?: (formApi: FormApi) => void;
    21  }
    22  
    23  require('./project-role-jwt-tokens.scss');
    24  
    25  export const ProjectRoleJWTTokens = (props: ProjectRoleJWTTokensProps) => {
    26      const [progress, setProgress] = React.useState<{percentage: number; title: string} | null>(null);
    27  
    28      return (
    29          <Consumer>
    30              {ctx => (
    31                  <React.Fragment>
    32                      <h4 style={{marginTop: '20px'}}>
    33                          JWT Tokens
    34                          {progress !== null && <ProgressPopup title={progress.title} percentage={progress.percentage} onClose={() => setProgress(null)} />}
    35                          <Button
    36                              title='Delete All Expired Tokens'
    37                              style={{marginLeft: '10px'}}
    38                              onClick={async () => {
    39                                  const expiredTokens = props.tokens.filter(token => new Date(token.exp * 1000) < new Date());
    40                                  if (expiredTokens.length === 0) {
    41                                      ctx.notifications.show({
    42                                          content: 'No expired tokens found for this role',
    43                                          type: NotificationType.Warning
    44                                      });
    45                                      return;
    46                                  }
    47                                  await deleteJWTTokens(props, ctx, expiredTokens, setProgress);
    48                              }}>
    49                              Delete Expired
    50                          </Button>
    51                      </h4>
    52                      <div>Generate JWT tokens to bind to this role</div>
    53                      {props.tokens && props.tokens.length > 0 && (
    54                          <div className='argo-table-list'>
    55                              <div className='argo-table-list__head'>
    56                                  <div className='row'>
    57                                      <div className='columns small-3'>ID</div>
    58                                      <div className='columns small-4'>ISSUED AT</div>
    59                                      <div className='columns small-4'>EXPIRES AT</div>
    60                                  </div>
    61                              </div>
    62                              {props.tokens.map(jwToken => renderJWTRow(props, ctx, jwToken))}
    63                          </div>
    64                      )}
    65                      <Form
    66                          getApi={props.getApi}
    67                          defaultValues={{expiresIn: ''}}
    68                          validateError={(params: any) => ({
    69                              expiresIn: !validExpiresIn(params.expiresIn) && 'Must be in the "[0-9]+[smhd]" format. For example, "12h", "7d".'
    70                          })}>
    71                          {api => (
    72                              <form onSubmit={api.submitForm} role='form' className='width-control'>
    73                                  <div className='white-box'>
    74                                      <div className='argo-table-list'>
    75                                          <div className='argo-form-row'>
    76                                              <FormField formApi={api} label='Token ID' field='id' component={Text} />
    77                                          </div>
    78                                          <div className='argo-form-row'>
    79                                              <FormField formApi={api} label='Expires In' field='expiresIn' component={Text} />
    80                                          </div>
    81  
    82                                          <div>
    83                                              <button className='argo-button argo-button--base' onClick={() => createJWTToken(props, api, ctx)}>
    84                                                  Create
    85                                              </button>
    86                                          </div>
    87                                      </div>
    88                                  </div>
    89  
    90                                  {props.token && (
    91                                      <div className='argo-table-list'>
    92                                          <div className='argo-table-list__head'>
    93                                              <div className='row'>
    94                                                  <div className='columns small-3'>NEW TOKEN</div>
    95                                              </div>
    96                                          </div>
    97                                          <div className='argo-table-list__row'>
    98                                              <div className='white-box'>
    99                                                  <p style={{overflowWrap: 'break-word'}}>
   100                                                      {props.token}
   101                                                      <i className='fa fa-times project-role-jwt-tokens__hide-token' onClick={() => props.hideJWTToken()} />
   102                                                  </p>
   103                                              </div>
   104                                          </div>
   105                                      </div>
   106                                  )}
   107                              </form>
   108                          )}
   109                      </Form>
   110                  </React.Fragment>
   111              )}
   112          </Consumer>
   113      );
   114  };
   115  
   116  async function createJWTToken(props: ProjectRoleJWTTokensProps, api: FormApi, ctx: any) {
   117      if (api.errors.expiresIn) {
   118          return;
   119      }
   120      const project = props.projName;
   121      const role = props.roleName;
   122      const expiresIn = convertExpiresInToSeconds(api.values.expiresIn);
   123      let expiresInPrompt = 'has no expiration';
   124      if (expiresIn !== 0) {
   125          expiresInPrompt = 'expires in ' + api.values.expiresIn;
   126      }
   127      const id = api.values.id;
   128      const nameContext = id === undefined || id === '' ? ` has no token ID and ` : ` has token ID '${id}' and `;
   129      const confirmed = await ctx.popup.confirm(
   130          'Create JWT Token',
   131          `Are you sure you want to create a JWT token that ${nameContext}${expiresInPrompt} for role '${role}' in project '${project}'?`
   132      );
   133      if (confirmed) {
   134          props.createJWTToken({project, role, expiresIn, id} as CreateJWTTokenParams);
   135          api.values.expiresIn = '';
   136          api.values.id = '';
   137      }
   138  }
   139  
   140  async function deleteJWTToken(props: ProjectRoleJWTTokensProps, iat: number, ctx: any, id: string) {
   141      const confirmed = await ctx.popup.confirm('Delete JWT Token', `Are you sure you want to delete token ID '${id}' for role '${props.roleName}' in project '${props.projName}'?`);
   142      if (confirmed) {
   143          props.deleteJWTToken({project: props.projName, role: props.roleName, iat} as DeleteJWTTokenParams);
   144      }
   145  }
   146  
   147  async function deleteJWTTokens(props: ProjectRoleJWTTokensProps, ctx: any, tokens: JwtToken[], setProgress: (progress: {percentage: number; title: string}) => void) {
   148      const confirmed = await ctx.popup.confirm(
   149          'Delete Expired JWT Tokens',
   150          `Are you sure you want to delete all ${tokens.length} expired tokens for role '${props.roleName}' in project '${props.projName}'?`
   151      );
   152      if (confirmed) {
   153          setProgress({percentage: 0, title: 'Deleting Expired Tokens'});
   154          let i = 0;
   155          const promises = tokens.map(token =>
   156              props.deleteJWTToken({project: props.projName, role: props.roleName, iat: token.iat} as DeleteJWTTokenParams).then(() => {
   157                  setProgress({percentage: Number(((i / tokens.length) * 100).toFixed(2)), title: `Deleting token ${i + 1} of ${tokens.length}(${token.id})`});
   158                  i++;
   159              })
   160          );
   161          await Promise.all(promises);
   162  
   163          setProgress({percentage: 100, title: 'Delete Expired Tokens Complete'});
   164      }
   165  }
   166  
   167  function renderJWTRow(props: ProjectRoleJWTTokensProps, ctx: ContextApis, jwToken: JwtToken): React.ReactFragment {
   168      const issuedAt = new Date(jwToken.iat * 1000).toISOString();
   169      const expiresAt = jwToken.exp == null ? 'Never' : new Date(jwToken.exp * 1000).toISOString();
   170      const isExpired = jwToken.exp && jwToken.exp < Date.now() / 1000;
   171  
   172      return (
   173          <div className='argo-table-list__row' key={`${jwToken.iat}`}>
   174              <div className='row'>
   175                  <div className='columns small-3 project-role-jwt-tokens__id'>
   176                      <Tooltip content={jwToken.id}>
   177                          <span className='project-role-jwt-tokens__id-tooltip'>{jwToken.id}</span>
   178                      </Tooltip>
   179                      {isExpired && (
   180                          <span title='Expired' className='project-role-jwt-tokens__expired-token'>
   181                              Expired
   182                          </span>
   183                      )}
   184                  </div>
   185                  <Tooltip content={issuedAt}>
   186                      <div className='columns small-4'>{issuedAt}</div>
   187                  </Tooltip>
   188                  <Tooltip content={expiresAt}>
   189                      <div className='columns small-4'>{expiresAt}</div>
   190                  </Tooltip>
   191                  <Tooltip content='Delete Token'>
   192                      <div className='columns small-1'>
   193                          <i className='fa fa-times' onClick={() => deleteJWTToken(props, jwToken.iat, ctx, jwToken.id)} style={{cursor: 'pointer'}} />
   194                      </div>
   195                  </Tooltip>
   196              </div>
   197          </div>
   198      );
   199  }