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 }