1<template> 2 <b-container fluid="xl"> 3 <page-title /> 4 <b-row> 5 <b-col xl="11"> 6 <!-- Expired certificates banner --> 7 <alert :show="expiredCertificateTypes.length > 0" variant="danger"> 8 <template v-if="expiredCertificateTypes.length > 1"> 9 {{ $t('pageCertificates.alert.certificatesExpiredMessage') }} 10 </template> 11 <template v-else> 12 {{ 13 $t('pageCertificates.alert.certificateExpiredMessage', { 14 certificate: expiredCertificateTypes[0], 15 }) 16 }} 17 </template> 18 </alert> 19 <!-- Expiring certificates banner --> 20 <alert :show="expiringCertificateTypes.length > 0" variant="warning"> 21 <template v-if="expiringCertificateTypes.length > 1"> 22 {{ $t('pageCertificates.alert.certificatesExpiringMessage') }} 23 </template> 24 <template v-else> 25 {{ 26 $t('pageCertificates.alert.certificateExpiringMessage', { 27 certificate: expiringCertificateTypes[0], 28 }) 29 }} 30 </template> 31 </alert> 32 <!-- Wrong file type banner --> 33 <alert :show="fileTypeCorrect === false" variant="danger"> 34 <template v-if="fileTypeCorrect === false"> 35 {{ $t('pageCertificates.alert.incorrectCertificateFileType') }} 36 </template> 37 </alert> 38 </b-col> 39 </b-row> 40 <b-row> 41 <b-col xl="11" class="text-right"> 42 <b-button 43 v-b-modal.generate-csr 44 data-test-id="certificates-button-generateCsr" 45 variant="link" 46 > 47 <icon-add /> 48 {{ $t('pageCertificates.generateCsr') }} 49 </b-button> 50 <b-button 51 variant="primary" 52 :disabled="certificatesForUpload.length === 0" 53 @click="initModalUploadCertificate(null)" 54 > 55 <icon-add /> 56 {{ $t('pageCertificates.addNewCertificate') }} 57 </b-button> 58 </b-col> 59 </b-row> 60 <b-row> 61 <b-col xl="11"> 62 <b-table 63 responsive="md" 64 show-empty 65 hover 66 :busy="isBusy" 67 :fields="fields" 68 :items="tableItems" 69 :empty-text="$t('global.table.emptyMessage')" 70 > 71 <template #cell(validFrom)="{ value }"> 72 {{ value | formatDate }} 73 </template> 74 75 <template #cell(validUntil)="{ value }"> 76 <status-icon 77 v-if="getDaysUntilExpired(value) < 31" 78 :status="getIconStatus(value)" 79 /> 80 {{ value | formatDate }} 81 </template> 82 83 <template #cell(actions)="{ value, item }"> 84 <table-row-action 85 v-for="(action, index) in value" 86 :key="index" 87 :value="action.value" 88 :title="action.title" 89 :enabled="action.enabled" 90 @click-table-action="onTableRowAction($event, item)" 91 > 92 <template #icon> 93 <icon-replace v-if="action.value === 'replace'" /> 94 <icon-trashcan v-if="action.value === 'delete'" /> 95 </template> 96 </table-row-action> 97 </template> 98 </b-table> 99 </b-col> 100 </b-row> 101 102 <!-- Modals --> 103 <modal-upload-certificate :certificate="modalCertificate" @ok="onModalOk" /> 104 <modal-generate-csr /> 105 </b-container> 106</template> 107 108<script> 109import IconAdd from '@carbon/icons-vue/es/add--alt/20'; 110import IconReplace from '@carbon/icons-vue/es/renew/20'; 111import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; 112 113import ModalGenerateCsr from './ModalGenerateCsr'; 114import ModalUploadCertificate from './ModalUploadCertificate'; 115import PageTitle from '@/components/Global/PageTitle'; 116import TableRowAction from '@/components/Global/TableRowAction'; 117import StatusIcon from '@/components/Global/StatusIcon'; 118import Alert from '@/components/Global/Alert'; 119 120import BVToastMixin from '@/components/Mixins/BVToastMixin'; 121import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; 122 123export default { 124 name: 'Certificates', 125 components: { 126 Alert, 127 IconAdd, 128 IconReplace, 129 IconTrashcan, 130 ModalGenerateCsr, 131 ModalUploadCertificate, 132 PageTitle, 133 StatusIcon, 134 TableRowAction, 135 }, 136 mixins: [BVToastMixin, LoadingBarMixin], 137 beforeRouteLeave(to, from, next) { 138 this.hideLoader(); 139 next(); 140 }, 141 data() { 142 return { 143 isBusy: true, 144 modalCertificate: null, 145 fileTypeCorrect: undefined, 146 fields: [ 147 { 148 key: 'certificate', 149 label: this.$t('pageCertificates.table.certificate'), 150 }, 151 { 152 key: 'issuedBy', 153 label: this.$t('pageCertificates.table.issuedBy'), 154 }, 155 { 156 key: 'issuedTo', 157 label: this.$t('pageCertificates.table.issuedTo'), 158 }, 159 { 160 key: 'validFrom', 161 label: this.$t('pageCertificates.table.validFrom'), 162 }, 163 { 164 key: 'validUntil', 165 label: this.$t('pageCertificates.table.validUntil'), 166 }, 167 { 168 key: 'actions', 169 label: '', 170 tdClass: 'text-right text-nowrap', 171 }, 172 ], 173 }; 174 }, 175 computed: { 176 certificates() { 177 return this.$store.getters['certificates/allCertificates']; 178 }, 179 tableItems() { 180 return this.certificates.map((certificate) => { 181 return { 182 ...certificate, 183 actions: [ 184 { 185 value: 'replace', 186 title: this.$t('pageCertificates.replaceCertificate'), 187 }, 188 { 189 value: 'delete', 190 title: this.$t('pageCertificates.deleteCertificate'), 191 enabled: 192 certificate.type === 'TrustStore Certificate' ? true : false, 193 }, 194 ], 195 }; 196 }); 197 }, 198 certificatesForUpload() { 199 return this.$store.getters['certificates/availableUploadTypes']; 200 }, 201 bmcTime() { 202 return this.$store.getters['global/bmcTime']; 203 }, 204 expiredCertificateTypes() { 205 return this.certificates.reduce((acc, val) => { 206 const daysUntilExpired = this.getDaysUntilExpired(val.validUntil); 207 if (daysUntilExpired < 1) { 208 acc.push(val.certificate); 209 } 210 return acc; 211 }, []); 212 }, 213 expiringCertificateTypes() { 214 return this.certificates.reduce((acc, val) => { 215 const daysUntilExpired = this.getDaysUntilExpired(val.validUntil); 216 if (daysUntilExpired < 31 && daysUntilExpired > 0) { 217 acc.push(val.certificate); 218 } 219 return acc; 220 }, []); 221 }, 222 }, 223 async created() { 224 this.startLoader(); 225 await this.$store.dispatch('global/getBmcTime'); 226 this.$store.dispatch('certificates/getCertificates').finally(() => { 227 this.endLoader(); 228 this.isBusy = false; 229 }); 230 }, 231 methods: { 232 onTableRowAction(event, rowItem) { 233 switch (event) { 234 case 'replace': 235 this.initModalUploadCertificate(rowItem); 236 break; 237 case 'delete': 238 this.initModalDeleteCertificate(rowItem); 239 break; 240 default: 241 break; 242 } 243 }, 244 initModalUploadCertificate(certificate = null) { 245 this.modalCertificate = certificate; 246 this.$bvModal.show('upload-certificate'); 247 }, 248 initModalDeleteCertificate(certificate) { 249 this.$bvModal 250 .msgBoxConfirm( 251 this.$t('pageCertificates.modal.deleteConfirmMessage', { 252 issuedBy: certificate.issuedBy, 253 certificate: certificate.certificate, 254 }), 255 { 256 title: this.$t('pageCertificates.deleteCertificate'), 257 okTitle: this.$t('global.action.delete'), 258 cancelTitle: this.$t('global.action.cancel'), 259 } 260 ) 261 .then((deleteConfirmed) => { 262 if (deleteConfirmed) this.deleteCertificate(certificate); 263 }); 264 }, 265 onModalOk({ addNew, file, type, location }) { 266 if (addNew) { 267 // Upload a new certificate 268 this.fileTypeCorrect = this.getIsFileTypeCorrect(file); 269 if (this.fileTypeCorrect) { 270 this.addNewCertificate(file, type); 271 } 272 } else { 273 // Replace an existing certificate 274 this.replaceCertificate(file, type, location); 275 } 276 }, 277 addNewCertificate(file, type) { 278 if (this.fileTypeCorrect === true) { 279 this.startLoader(); 280 this.$store 281 .dispatch('certificates/addNewCertificate', { file, type }) 282 .then((success) => this.successToast(success)) 283 .catch(({ message }) => this.errorToast(message)) 284 .finally(() => this.endLoader()); 285 } 286 }, 287 replaceCertificate(file, type, location) { 288 this.startLoader(); 289 const reader = new FileReader(); 290 reader.readAsBinaryString(file); 291 reader.onloadend = (event) => { 292 const certificateString = event.target.result; 293 this.$store 294 .dispatch('certificates/replaceCertificate', { 295 certificateString, 296 type, 297 location, 298 }) 299 .then((success) => this.successToast(success)) 300 .catch(({ message }) => this.errorToast(message)) 301 .finally(() => this.endLoader()); 302 }; 303 }, 304 deleteCertificate({ type, location }) { 305 this.startLoader(); 306 this.$store 307 .dispatch('certificates/deleteCertificate', { 308 type, 309 location, 310 }) 311 .then((success) => this.successToast(success)) 312 .catch(({ message }) => this.errorToast(message)) 313 .finally(() => this.endLoader()); 314 }, 315 getDaysUntilExpired(date) { 316 if (this.bmcTime) { 317 const validUntilMs = date.getTime(); 318 const currentBmcTimeMs = this.bmcTime.getTime(); 319 const oneDayInMs = 24 * 60 * 60 * 1000; 320 return Math.round((validUntilMs - currentBmcTimeMs) / oneDayInMs); 321 } 322 return new Date(); 323 }, 324 getIconStatus(date) { 325 const daysUntilExpired = this.getDaysUntilExpired(date); 326 if (daysUntilExpired < 1) { 327 return 'danger'; 328 } else if (daysUntilExpired < 31) { 329 return 'warning'; 330 } 331 }, 332 getIsFileTypeCorrect(file) { 333 const fileTypeExtension = file.name.split('.').pop(); 334 return fileTypeExtension === 'pem'; 335 }, 336 }, 337}; 338</script> 339