github.com/argoproj/argo-cd/v2@v2.10.9/ui/src/app/settings/components/gpgkeys-list/gpgkeys-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, 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('./gpgkeys-list.scss'); 13 14 interface NewGnuPGPublicKeyParams { 15 keyData: string; 16 } 17 18 export class GpgKeysList extends React.Component<RouteComponentProps<any>> { 19 public static contextTypes = { 20 router: PropTypes.object, 21 apis: PropTypes.object, 22 history: PropTypes.object 23 }; 24 25 private formApi: FormApi; 26 private loader: DataLoader; 27 28 public render() { 29 return ( 30 <Page 31 title='GnuPG public keys' 32 toolbar={{ 33 breadcrumbs: [{title: 'Settings', path: '/settings'}, {title: 'GnuPG public keys'}], 34 actionMenu: { 35 className: 'fa fa-plus', 36 items: [ 37 { 38 title: 'Add GnuPG key', 39 iconClassName: 'fa fa-plus', 40 action: () => (this.showAddGnuPGKey = true) 41 } 42 ] 43 } 44 }}> 45 <div className='gpgkeys-list'> 46 <div className='argo-container'> 47 <DataLoader load={() => services.gpgkeys.list()} ref={loader => (this.loader = loader)}> 48 {(gpgkeys: models.GnuPGPublicKey[]) => 49 (gpgkeys.length > 0 && ( 50 <div className='argo-table-list'> 51 <div className='argo-table-list__head'> 52 <div className='row'> 53 <div className='columns small-3'>KEY ID</div> 54 <div className='columns small-3'>KEY TYPE</div> 55 <div className='columns small-6'>IDENTITY</div> 56 </div> 57 </div> 58 {gpgkeys.map(gpgkey => ( 59 <div className='argo-table-list__row' key={gpgkey.keyID}> 60 <div className='row'> 61 <div className='columns small-3'> 62 <i className='fa fa-key' /> {gpgkey.keyID} 63 </div> 64 <div className='columns small-3'>{gpgkey.subType.toUpperCase()}</div> 65 <div className='columns small-6'> 66 {gpgkey.owner} 67 <DropDownMenu 68 anchor={() => ( 69 <button className='argo-button argo-button--light argo-button--lg argo-button--short'> 70 <i className='fa fa-ellipsis-v' /> 71 </button> 72 )} 73 items={[ 74 { 75 title: 'Remove', 76 action: () => this.removeKey(gpgkey.keyID) 77 } 78 ]} 79 /> 80 </div> 81 </div> 82 </div> 83 ))} 84 </div> 85 )) || ( 86 <EmptyState icon='fa fa-key'> 87 <h4>No GnuPG public keys currently configured</h4> 88 <h5>You can add GnuPG public keys below..</h5> 89 <button className='argo-button argo-button--base' onClick={() => (this.showAddGnuPGKey = true)}> 90 Add GnuPG public key 91 </button> 92 </EmptyState> 93 ) 94 } 95 </DataLoader> 96 </div> 97 </div> 98 <SlidingPanel 99 isShown={this.showAddGnuPGKey} 100 onClose={() => (this.showAddGnuPGKey = false)} 101 header={ 102 <div> 103 <button className='argo-button argo-button--base' onClick={() => this.formApi.submitForm(null)}> 104 Create 105 </button>{' '} 106 <button onClick={() => (this.showAddGnuPGKey = false)} className='argo-button argo-button--base-o'> 107 Cancel 108 </button> 109 </div> 110 }> 111 <Form 112 onSubmit={params => this.addGnuPGPublicKey({keyData: params.keyData})} 113 getApi={api => (this.formApi = api)} 114 preSubmit={(params: NewGnuPGPublicKeyParams) => ({ 115 keyData: params.keyData 116 })} 117 validateError={(params: NewGnuPGPublicKeyParams) => ({ 118 keyData: !params.keyData && 'GnuPG public key data is required' 119 })}> 120 {formApi => ( 121 <form onSubmit={formApi.submitForm} role='form' className='gpgkeys-list width-control' encType='multipart/form-data'> 122 <div className='white-box'> 123 <p>ADD GnuPG PUBLIC KEY</p> 124 <div className='argo-form-row'> 125 <FormField formApi={formApi} label='GnuPG public key data (ASCII-armored)' field='keyData' component={TextArea} /> 126 </div> 127 </div> 128 </form> 129 )} 130 </Form> 131 </SlidingPanel> 132 </Page> 133 ); 134 } 135 136 private clearForms() { 137 this.formApi.resetAll(); 138 } 139 140 private validateKeyInputfield(data: string): boolean { 141 if (data == null || data === '') { 142 return false; 143 } 144 const str = data.trim(); 145 const startNeedle = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n'; 146 const endNeedle = '\n-----END PGP PUBLIC KEY BLOCK-----'; 147 148 if (str.length < startNeedle.length + endNeedle.length) { 149 return false; 150 } 151 if (!str.startsWith(startNeedle)) { 152 return false; 153 } 154 if (!str.endsWith(endNeedle)) { 155 return false; 156 } 157 return true; 158 } 159 160 private async addGnuPGPublicKey(params: NewGnuPGPublicKeyParams) { 161 try { 162 if (!this.validateKeyInputfield(params.keyData)) { 163 throw { 164 name: 'Invalid key exception', 165 message: 'Invalid GnuPG key data found - must be ASCII armored' 166 }; 167 } 168 await services.gpgkeys.create({keyData: params.keyData}); 169 this.showAddGnuPGKey = false; 170 this.loader.reload(); 171 } catch (e) { 172 this.appContext.apis.notifications.show({ 173 content: <ErrorNotification title='Unable to add GnuPG public key' e={e} />, 174 type: NotificationType.Error 175 }); 176 } 177 } 178 179 private async removeKey(keyId: string) { 180 const confirmed = await this.appContext.apis.popup.confirm('Remove GPG public key', 'Are you sure you want to remove GPG key with ID ' + keyId + '?'); 181 if (confirmed) { 182 await services.gpgkeys.delete(keyId); 183 this.loader.reload(); 184 } 185 } 186 187 private get showAddGnuPGKey() { 188 return new URLSearchParams(this.props.location.search).get('addGnuPGPublicKey') === 'true'; 189 } 190 191 private set showAddGnuPGKey(val: boolean) { 192 this.clearForms(); 193 this.appContext.router.history.push(`${this.props.match.url}?addGnuPGPublicKey=${val}`); 194 } 195 196 private get appContext(): AppContext { 197 return this.context as AppContext; 198 } 199 }