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