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 fields: [ 140 { 141 key: 'certificate', 142 label: this.$t('pageCertificates.table.certificate'), 143 }, 144 { 145 key: 'issuedBy', 146 label: this.$t('pageCertificates.table.issuedBy'), 147 }, 148 { 149 key: 'issuedTo', 150 label: this.$t('pageCertificates.table.issuedTo'), 151 }, 152 { 153 key: 'validFrom', 154 label: this.$t('pageCertificates.table.validFrom'), 155 }, 156 { 157 key: 'validUntil', 158 label: this.$t('pageCertificates.table.validUntil'), 159 }, 160 { 161 key: 'actions', 162 label: '', 163 tdClass: 'text-right text-nowrap', 164 }, 165 ], 166 }; 167 }, 168 computed: { 169 certificates() { 170 return this.$store.getters['certificates/allCertificates']; 171 }, 172 tableItems() { 173 return this.certificates.map((certificate) => { 174 return { 175 ...certificate, 176 actions: [ 177 { 178 value: 'replace', 179 title: this.$t('pageCertificates.replaceCertificate'), 180 }, 181 { 182 value: 'delete', 183 title: this.$t('pageCertificates.deleteCertificate'), 184 enabled: 185 certificate.type === 'TrustStore Certificate' ? true : false, 186 }, 187 ], 188 }; 189 }); 190 }, 191 certificatesForUpload() { 192 return this.$store.getters['certificates/availableUploadTypes']; 193 }, 194 bmcTime() { 195 return this.$store.getters['global/bmcTime']; 196 }, 197 expiredCertificateTypes() { 198 return this.certificates.reduce((acc, val) => { 199 const daysUntilExpired = this.getDaysUntilExpired(val.validUntil); 200 if (daysUntilExpired < 1) { 201 acc.push(val.certificate); 202 } 203 return acc; 204 }, []); 205 }, 206 expiringCertificateTypes() { 207 return this.certificates.reduce((acc, val) => { 208 const daysUntilExpired = this.getDaysUntilExpired(val.validUntil); 209 if (daysUntilExpired < 31 && daysUntilExpired > 0) { 210 acc.push(val.certificate); 211 } 212 return acc; 213 }, []); 214 }, 215 }, 216 async created() { 217 this.startLoader(); 218 await this.$store.dispatch('global/getBmcTime'); 219 this.$store.dispatch('certificates/getCertificates').finally(() => { 220 this.endLoader(); 221 this.isBusy = false; 222 }); 223 }, 224 methods: { 225 onTableRowAction(event, rowItem) { 226 switch (event) { 227 case 'replace': 228 this.initModalUploadCertificate(rowItem); 229 break; 230 case 'delete': 231 this.initModalDeleteCertificate(rowItem); 232 break; 233 default: 234 break; 235 } 236 }, 237 initModalUploadCertificate(certificate = null) { 238 this.modalCertificate = certificate; 239 this.$bvModal.show('upload-certificate'); 240 }, 241 initModalDeleteCertificate(certificate) { 242 this.$bvModal 243 .msgBoxConfirm( 244 this.$t('pageCertificates.modal.deleteConfirmMessage', { 245 issuedBy: certificate.issuedBy, 246 certificate: certificate.certificate, 247 }), 248 { 249 title: this.$t('pageCertificates.deleteCertificate'), 250 okTitle: this.$t('global.action.delete'), 251 cancelTitle: this.$t('global.action.cancel'), 252 } 253 ) 254 .then((deleteConfirmed) => { 255 if (deleteConfirmed) this.deleteCertificate(certificate); 256 }); 257 }, 258 onModalOk({ addNew, file, type, location }) { 259 if (addNew) { 260 // Upload a new certificate 261 this.addNewCertificate(file, type); 262 } else { 263 // Replace an existing certificate 264 this.replaceCertificate(file, type, location); 265 } 266 }, 267 addNewCertificate(file, type) { 268 this.startLoader(); 269 this.$store 270 .dispatch('certificates/addNewCertificate', { file, type }) 271 .then((success) => this.successToast(success)) 272 .catch(({ message }) => this.errorToast(message)) 273 .finally(() => this.endLoader()); 274 }, 275 replaceCertificate(file, type, location) { 276 this.startLoader(); 277 const reader = new FileReader(); 278 reader.readAsBinaryString(file); 279 reader.onloadend = (event) => { 280 const certificateString = event.target.result; 281 this.$store 282 .dispatch('certificates/replaceCertificate', { 283 certificateString, 284 type, 285 location, 286 }) 287 .then((success) => this.successToast(success)) 288 .catch(({ message }) => this.errorToast(message)) 289 .finally(() => this.endLoader()); 290 }; 291 }, 292 deleteCertificate({ type, location }) { 293 this.startLoader(); 294 this.$store 295 .dispatch('certificates/deleteCertificate', { 296 type, 297 location, 298 }) 299 .then((success) => this.successToast(success)) 300 .catch(({ message }) => this.errorToast(message)) 301 .finally(() => this.endLoader()); 302 }, 303 getDaysUntilExpired(date) { 304 if (this.bmcTime) { 305 const validUntilMs = date.getTime(); 306 const currentBmcTimeMs = this.bmcTime.getTime(); 307 const oneDayInMs = 24 * 60 * 60 * 1000; 308 return Math.round((validUntilMs - currentBmcTimeMs) / oneDayInMs); 309 } 310 return new Date(); 311 }, 312 getIconStatus(date) { 313 const daysUntilExpired = this.getDaysUntilExpired(date); 314 if (daysUntilExpired < 1) { 315 return 'danger'; 316 } else if (daysUntilExpired < 31) { 317 return 'warning'; 318 } 319 }, 320 }, 321}; 322</script> 323