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