Add SSL Certificate expiration warnings

Adds status icons in data table and alert banners for
expiring and expired certificates.
Warning will be visible within 30 days of certificate
expiration.
Critical/danger indicators will be visible when certificate
is expired.

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I6f8c27d4ba1563a79b46eec7b869366ecee42f75
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index d1d5f61..22bb514 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -238,6 +238,12 @@
     "httpsCertificate": "HTTPS Certificate",
     "ldapCertificate": "LDAP Certificate",
     "replaceCertificate": "Replace certificate",
+    "alert": {
+      "certificateExpiredMessage": "%{certificate} has expired. Consider replacing it with a new certificate.",
+      "certificateExpiringMessage": "%{certificate} is expiring soon. Consider replacing it with a new certificate.",
+      "certificatesExpiredMessage": "Some certificates have expired. Consider replacing them with new certificates.",
+      "certificatesExpiringMessage": "Some certificates are expiring soon. Consider replacing them with new certificates."
+    },
     "modal": {
       "certificateType": "Certificate type",
       "certificateFile": "Certificate file",
diff --git a/src/store/modules/GlobalStore.js b/src/store/modules/GlobalStore.js
index dd12fc2..6511a45 100644
--- a/src/store/modules/GlobalStore.js
+++ b/src/store/modules/GlobalStore.js
@@ -25,7 +25,7 @@
   namespaced: true,
   state: {
     hostName: '--',
-    bmcTime: '--',
+    bmcTime: null,
     hostStatus: 'unreachable'
   },
   getters: {
diff --git a/src/views/AccessControl/SslCertificates/SslCertificates.vue b/src/views/AccessControl/SslCertificates/SslCertificates.vue
index ae28271..313b321 100644
--- a/src/views/AccessControl/SslCertificates/SslCertificates.vue
+++ b/src/views/AccessControl/SslCertificates/SslCertificates.vue
@@ -2,6 +2,36 @@
   <b-container fluid>
     <page-title />
     <b-row>
+      <b-col xl="9">
+        <!-- Expired certificates banner -->
+        <alert :show="expiredCertificateTypes.length > 0" variant="danger">
+          <template v-if="expiredCertificateTypes.length > 1">
+            {{ $t('pageSslCertificates.alert.certificatesExpiredMessage') }}
+          </template>
+          <template v-else>
+            {{
+              $t('pageSslCertificates.alert.certificateExpiredMessage', {
+                certificate: expiredCertificateTypes[0]
+              })
+            }}
+          </template>
+        </alert>
+        <!-- Expiring certificates banner -->
+        <alert :show="expiringCertificateTypes.length > 0" variant="warning">
+          <template v-if="expiringCertificateTypes.length > 1">
+            {{ $t('pageSslCertificates.alert.certificatesExpiringMessage') }}
+          </template>
+          <template v-else>
+            {{
+              $t('pageSslCertificates.alert.certificateExpiringMessage', {
+                certificate: expiringCertificateTypes[0]
+              })
+            }}
+          </template>
+        </alert>
+      </b-col>
+    </b-row>
+    <b-row>
       <b-col xl="9" class="text-right">
         <b-button
           variant="primary"
@@ -21,6 +51,10 @@
           </template>
 
           <template v-slot:cell(validUntil)="{ value }">
+            <status-icon
+              v-if="getDaysUntilExpired(value) < 31"
+              :status="getIconStatus(value)"
+            />
             {{ value | formatDate }}
           </template>
 
@@ -56,17 +90,21 @@
 import ModalUploadCertificate from './ModalUploadCertificate';
 import PageTitle from '../../../components/Global/PageTitle';
 import TableRowAction from '../../../components/Global/TableRowAction';
+import StatusIcon from '../../../components/Global/StatusIcon';
+import Alert from '../../../components/Global/Alert';
 
 import BVToastMixin from '../../../components/Mixins/BVToastMixin';
 
 export default {
   name: 'SslCertificates',
   components: {
+    Alert,
     IconAdd,
     IconReplace,
     IconTrashcan,
     ModalUploadCertificate,
     PageTitle,
+    StatusIcon,
     TableRowAction
   },
   mixins: [BVToastMixin],
@@ -127,10 +165,32 @@
     },
     certificatesForUpload() {
       return this.$store.getters['sslCertificates/availableUploadTypes'];
+    },
+    bmcTime() {
+      return this.$store.getters['global/bmcTime'];
+    },
+    expiredCertificateTypes() {
+      return this.certificates.reduce((acc, val) => {
+        const daysUntilExpired = this.getDaysUntilExpired(val.validUntil);
+        if (daysUntilExpired < 1) {
+          acc.push(val.certificate);
+        }
+        return acc;
+      }, []);
+    },
+    expiringCertificateTypes() {
+      return this.certificates.reduce((acc, val) => {
+        const daysUntilExpired = this.getDaysUntilExpired(val.validUntil);
+        if (daysUntilExpired < 31 && daysUntilExpired > 0) {
+          acc.push(val.certificate);
+        }
+        return acc;
+      }, []);
     }
   },
   created() {
     this.$store.dispatch('sslCertificates/getCertificates');
+    this.$store.dispatch('global/getBmcTime');
   },
   methods: {
     onTableRowAction(event, rowItem) {
@@ -203,6 +263,23 @@
         })
         .then(success => this.successToast(success))
         .catch(({ message }) => this.errorToast(message));
+    },
+    getDaysUntilExpired(date) {
+      if (this.bmcTime) {
+        const validUntilMs = date.getTime();
+        const currentBmcTimeMs = this.bmcTime.getTime();
+        const oneDayInMs = 24 * 60 * 60 * 1000;
+        return Math.round((validUntilMs - currentBmcTimeMs) / oneDayInMs);
+      }
+      return null;
+    },
+    getIconStatus(date) {
+      const daysUntilExpired = this.getDaysUntilExpired(date);
+      if (daysUntilExpired < 1) {
+        return 'danger';
+      } else if (daysUntilExpired < 31) {
+        return 'warning';
+      }
     }
   }
 };
diff --git a/src/views/Overview/OverviewQuickLinks.vue b/src/views/Overview/OverviewQuickLinks.vue
index 9f3ba52..3a0b9cf 100644
--- a/src/views/Overview/OverviewQuickLinks.vue
+++ b/src/views/Overview/OverviewQuickLinks.vue
@@ -3,7 +3,10 @@
     <div>
       <dl>
         <dt>{{ $t('pageOverview.quicklinks.bmcTime') }}</dt>
-        <dd>{{ bmcTime | formatDate }} {{ bmcTime | formatTime }}</dd>
+        <dd v-if="bmcTime">
+          {{ bmcTime | formatDate }} {{ bmcTime | formatTime }}
+        </dd>
+        <dd v-else>--</dd>
       </dl>
     </div>
     <div>