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