github.com/argoproj/argo-cd@v1.8.7/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 action: () => (this.showAddGnuPGKey = true) 40 } 41 ] 42 } 43 }}> 44 <div className='gpgkeys-list'> 45 <div className='argo-container'> 46 <DataLoader load={() => services.gpgkeys.list()} ref={loader => (this.loader = loader)}> 47 {(gpgkeys: models.GnuPGPublicKey[]) => 48 (gpgkeys.length > 0 && ( 49 <div className='argo-table-list'> 50 <div className='argo-table-list__head'> 51 <div className='row'> 52 <div className='columns small-3'>KEY ID</div> 53 <div className='columns small-3'>KEY TYPE</div> 54 <div className='columns small-6'>IDENTITY</div> 55 </div> 56 </div> 57 {gpgkeys.map(gpgkey => ( 58 <div className='argo-table-list__row' key={gpgkey.keyID}> 59 <div className='row'> 60 <div className='columns small-3'> 61 <i className='fa fa-key' /> {gpgkey.keyID} 62 </div> 63 <div className='columns small-3'>{gpgkey.subType.toUpperCase()}</div> 64 <div className='columns small-6'> 65 {gpgkey.owner} 66 <DropDownMenu 67 anchor={() => ( 68 <button className='argo-button argo-button--light argo-button--lg argo-button--short'> 69 <i className='fa fa-ellipsis-v' /> 70 </button> 71 )} 72 items={[ 73 { 74 title: 'Remove', 75 action: () => this.removeKey(gpgkey.keyID) 76 } 77 ]} 78 /> 79 </div> 80 </div> 81 </div> 82 ))} 83 </div> 84 )) || ( 85 <EmptyState icon='fa fa-key'> 86 <h4>No GnuPG public keys currently configured</h4> 87 <h5>You can add GnuPG public keys below..</h5> 88 <button className='argo-button argo-button--base' onClick={() => (this.showAddGnuPGKey = true)}> 89 Add GnuPG public key 90 </button> 91 </EmptyState> 92 ) 93 } 94 </DataLoader> 95 </div> 96 </div> 97 <SlidingPanel 98 isShown={this.showAddGnuPGKey} 99 onClose={() => (this.showAddGnuPGKey = false)} 100 header={ 101 <div> 102 <button className='argo-button argo-button--base' onClick={() => this.formApi.submitForm(null)}> 103 Create 104 </button>{' '} 105 <button onClick={() => (this.showAddGnuPGKey = false)} className='argo-button argo-button--base-o'> 106 Cancel 107 </button> 108 </div> 109 }> 110 <h4>Add GnuPG public key</h4> 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 && '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='argo-form-row'> 123 <FormField formApi={formApi} label='GnuPG public key data (ASCII-armored)' field='keyData' component={TextArea} /> 124 </div> 125 </form> 126 )} 127 </Form> 128 </SlidingPanel> 129 </Page> 130 ); 131 } 132 133 private clearForms() { 134 this.formApi.resetAll(); 135 } 136 137 private validateKeyInputfield(data: string): boolean { 138 if (data == null || data === '') { 139 return false; 140 } 141 const str = data.trim(); 142 const startNeedle = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n'; 143 const endNeedle = '\n-----END PGP PUBLIC KEY BLOCK-----'; 144 145 if (str.length < startNeedle.length + endNeedle.length) { 146 return false; 147 } 148 if (!str.startsWith(startNeedle)) { 149 return false; 150 } 151 if (!str.endsWith(endNeedle)) { 152 return false; 153 } 154 return true; 155 } 156 157 private async addGnuPGPublicKey(params: NewGnuPGPublicKeyParams) { 158 try { 159 if (!this.validateKeyInputfield(params.keyData)) { 160 throw { 161 name: 'Invalid key exception', 162 message: 'Invalid GnuPG key data found - must be ASCII armored' 163 }; 164 } 165 await services.gpgkeys.create({keyData: params.keyData}); 166 this.showAddGnuPGKey = false; 167 this.loader.reload(); 168 } catch (e) { 169 this.appContext.apis.notifications.show({ 170 content: <ErrorNotification title='Unable to add GnuPG public key' e={e} />, 171 type: NotificationType.Error 172 }); 173 } 174 } 175 176 private async removeKey(keyId: string) { 177 const confirmed = await this.appContext.apis.popup.confirm('Remove GPG public key', 'Are you sure you want to remove GPG key with ID ' + keyId + '?'); 178 if (confirmed) { 179 await services.gpgkeys.delete(keyId); 180 this.loader.reload(); 181 } 182 } 183 184 private get showAddGnuPGKey() { 185 return new URLSearchParams(this.props.location.search).get('addGnuPGPublicKey') === 'true'; 186 } 187 188 private set showAddGnuPGKey(val: boolean) { 189 this.clearForms(); 190 this.appContext.router.history.push(`${this.props.match.url}?addGnuPGPublicKey=${val}`); 191 } 192 193 private get appContext(): AppContext { 194 return this.context as AppContext; 195 } 196 }