Add Health status to app header

Added logging path and interface to websocket subscription
data filter, to dynamically indicate Health status in the
app header.

- Update OverviewEvents to use highPriorityEvents data
- Refactor EventLogStore

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I35ad30b005c70625a5f6a69488d45db0fa049374
diff --git a/src/components/AppHeader/AppHeader.vue b/src/components/AppHeader/AppHeader.vue
index 880c428..d411c1f 100644
--- a/src/components/AppHeader/AppHeader.vue
+++ b/src/components/AppHeader/AppHeader.vue
@@ -14,7 +14,7 @@
           <b-nav>
             <b-nav-item>
               Health
-              <status-icon :status="'danger'" />
+              <status-icon :status="healthStatusIcon" />
             </b-nav-item>
             <b-nav-item>
               Power
@@ -46,6 +46,9 @@
     hostStatus() {
       return this.$store.getters['global/hostStatus'];
     },
+    healthStatus() {
+      return this.$store.getters['eventLog/healthStatus'];
+    },
     hostStatusIcon() {
       switch (this.hostStatus) {
         case 'on':
@@ -56,15 +59,31 @@
         default:
           return 'secondary';
       }
+    },
+    healthStatusIcon() {
+      switch (this.healthStatus) {
+        case 'good':
+          return 'success';
+        case 'warning':
+          return 'warning';
+        case 'critical':
+          return 'danger';
+        default:
+          return 'secondary';
+      }
     }
   },
   created() {
     this.getHostInfo();
+    this.getEvents();
   },
   methods: {
     getHostInfo() {
       this.$store.dispatch('global/getHostStatus');
     },
+    getEvents() {
+      this.$store.dispatch('eventLog/getEventLogData');
+    },
     logout() {
       this.$store.dispatch('authentication/logout');
     }
@@ -84,6 +103,12 @@
     transition-timing-function: cubic-bezier(0, 0, 0.3, 1);
   }
 }
+.navbar-dark {
+  .navbar-text,
+  .nav-link {
+    color: $white !important;
+  }
+}
 .nav-item {
   svg {
     fill: $light;
diff --git a/src/components/Global/StatusIcon.vue b/src/components/Global/StatusIcon.vue
index a2c7f04..d59eaec 100644
--- a/src/components/Global/StatusIcon.vue
+++ b/src/components/Global/StatusIcon.vue
@@ -1,6 +1,7 @@
 <template>
   <span :class="['status-icon', status]">
     <icon-success v-if="status === 'success'" />
+    <icon-warning v-else-if="status === 'warning'" />
     <icon-danger v-else-if="status === 'danger'" />
     <icon-secondary v-else />
   </span>
@@ -15,8 +16,9 @@
   name: 'StatusIcon',
   components: {
     iconSuccess: IconCheckmark,
-    iconDanger: IconWarning,
-    iconSecondary: IconError
+    iconDanger: IconError,
+    iconSecondary: IconError, //TODO: swap with right asset when available
+    iconWarning: IconWarning
   },
   props: {
     status: {
@@ -39,5 +41,8 @@
   &.secondary {
     fill: $secondary;
   }
+  &.warning {
+    fill: $warning;
+  }
 }
 </style>
diff --git a/src/store/modules/GlobalStore.js b/src/store/modules/GlobalStore.js
index 18d5043..21ea796 100644
--- a/src/store/modules/GlobalStore.js
+++ b/src/store/modules/GlobalStore.js
@@ -37,7 +37,7 @@
     setHostName: (state, hostName) => (state.hostName = hostName),
     setBmcTime: (state, bmcTime) => (state.bmcTime = bmcTime),
     setHostStatus: (state, hostState) =>
-      (state.hostState = hostStateMapper(hostState))
+      (state.hostStatus = hostStateMapper(hostState))
   },
   actions: {
     getHostName({ commit }) {
diff --git a/src/store/modules/Health/EventLogStore.js b/src/store/modules/Health/EventLogStore.js
index 404a963..3f32ab1 100644
--- a/src/store/modules/Health/EventLogStore.js
+++ b/src/store/modules/Health/EventLogStore.js
@@ -1,70 +1,116 @@
 import api from '../../api';
 
-const severityToPriorityMap = {
-  Emergency: 'High',
-  Alert: 'High',
-  Critical: 'High',
-  Error: 'High',
-  Warning: 'Medium',
-  Notice: 'Low',
-  Debug: 'Low',
-  Informational: 'Low'
+const EVENT_SEVERITY = {
+  emergency: 'xyz.openbmc_project.Logging.Entry.Level.Emergency',
+  alert: 'xyz.openbmc_project.Logging.Entry.Level.Alert',
+  critical: 'xyz.openbmc_project.Logging.Entry.Level.Critical',
+  error: 'xyz.openbmc_project.Logging.Entry.Level.Error',
+  warning: 'xyz.openbmc_project.Logging.Entry.Level.Warning',
+  notice: 'xyz.openbmc_project.Logging.Entry.Level.Notice',
+  informational: 'xyz.openbmc_project.Logging.Entry.Level.Informational',
+  debug: 'xyz.openbmc_project.Logging.Entry.Level.Debug'
+};
+
+const priorityMapper = severity => {
+  switch (severity) {
+    case EVENT_SEVERITY.emergency:
+    case EVENT_SEVERITY.alert:
+    case EVENT_SEVERITY.critical:
+    case EVENT_SEVERITY.error:
+      return 'high';
+    case EVENT_SEVERITY.warning:
+      return 'medium';
+    case EVENT_SEVERITY.notice:
+    case EVENT_SEVERITY.debug:
+    case EVENT_SEVERITY.informational:
+      return 'low';
+    default:
+      return '';
+  }
+};
+
+const getHealthStatus = allEvents => {
+  let status = 'good';
+  for (const event of allEvents) {
+    if (!event.Resolved && event.priority === 'medium') {
+      status = 'warning';
+    }
+    if (!event.Resolved && event.priority === 'high') {
+      status = 'critical';
+      break;
+    }
+  }
+  return status;
 };
 
 const EventLogStore = {
   namespaced: true,
   state: {
-    eventLogData: null
+    allEvents: [],
+    highPriorityEvents: [],
+    healthStatus: null
   },
   getters: {
-    eventLogData: state => state.eventLogData
+    allEvents: state => state.allEvents,
+    highPriorityEvents: state => state.highPriorityEvents,
+    healthStatus: state => state.healthStatus
   },
   mutations: {
-    setEventLogData: (state, eventLogData) =>
-      (state.eventLogData = eventLogData)
+    setAllEvents: (state, allEvents) => (state.allEvents = allEvents),
+    setHighPriorityEvents: (state, highPriorityEvents) =>
+      (state.highPriorityEvents = highPriorityEvents),
+    setHealthStatus: (state, status) => (state.healthStatus = status)
   },
   actions: {
     getEventLogData({ commit }) {
       api
         .get('/xyz/openbmc_project/logging/enumerate')
         .then(response => {
-          const eventLog = response.data.data;
-          const entryNumber = /[1-9]/;
-          const eventLogEntries = [];
-          /**
-           * Entry log endpoints:
-           * 'entry' + entry id contain event log entry information
-           * 'callout' contains part number and serial number for part affected
-           */
-          for (let key in eventLog) {
-            // Check for event log entry:
-            if (
-              key.includes('entry') &&
-              key.match(entryNumber) &&
-              !key.includes('callout')
-            ) {
-              const eventKey = eventLog[key];
-              const eventSeverity = eventKey.Severity.split('.').pop();
-              const eventPriority = severityToPriorityMap[eventSeverity];
-              eventLogEntries.push(
-                Object.assign(
-                  {
-                    logId: eventKey.Id,
-                    priority: eventPriority,
-                    timestamp: eventKey.Timestamp,
-                    eventID: eventKey.EventID,
-                    description: eventKey.Description
-                  },
-                  eventKey
-                )
-              );
-              commit('setEventLogData', eventLogEntries);
+          const responseData = response.data.data;
+          const eventLogs = [];
+          for (const key in responseData) {
+            const event = responseData[key];
+            const { Id } = event;
+            if (responseData.hasOwnProperty(key) && Id) {
+              const { EventID, Description, Timestamp, Severity } = event;
+              eventLogs.push({
+                logId: Id,
+                priority: priorityMapper(Severity),
+                timestamp: Timestamp,
+                eventID: EventID,
+                description: Description,
+                ...event
+              });
             }
           }
+
+          const healthStatus = getHealthStatus(eventLogs);
+          const highPriorityEvents = eventLogs.filter(
+            ({ priority, Resolved }) => priority === 'high' && !Resolved
+          );
+
+          commit('setAllEvents', eventLogs);
+          commit('setHighPriorityEvents', highPriorityEvents);
+          commit('setHealthStatus', healthStatus);
         })
         .catch(error => {
           console.log('Event Log Data:', error);
         });
+    },
+    checkHealth({ commit, getters }, interfaces) {
+      if (getters['healthStatus'] === 'critical') return;
+      for (const key in interfaces) {
+        const event = interfaces[key];
+        const eventPriority = priorityMapper(event.Severity);
+        const isEventResolved = event.Resolved;
+        if (!isEventResolved) {
+          if (eventPriority === 'high') {
+            commit('setHealthStatus', 'critical');
+            break;
+          }
+          if (eventPriority === 'medium') commit('setHealthStatus', 'warning');
+        }
+      }
     }
   }
 };
diff --git a/src/store/plugins/WebSocketPlugin.js b/src/store/plugins/WebSocketPlugin.js
index 3e2139d..409b168 100644
--- a/src/store/plugins/WebSocketPlugin.js
+++ b/src/store/plugins/WebSocketPlugin.js
@@ -2,16 +2,19 @@
  * WebSocketPlugin will allow us to get new data from the server
  * without having to poll for changes on the frontend.
  *
- * This plugin is subscribed to host state property changes, which
- * is indicated in the app header Power status.
+ * This plugin is subscribed to host state property and logging
+ * changes, indicated in the app header Health and Power status.
  *
  * https://github.com/openbmc/docs/blob/b41aff0fabe137cdb0cfff584b5fe4a41c0c8e77/rest-api.md#event-subscription-protocol
  */
 const WebSocketPlugin = store => {
   let ws;
   const data = {
-    paths: ['/xyz/openbmc_project/state/host0'],
-    interfaces: ['xyz.openbmc_project.State.Host']
+    paths: ['/xyz/openbmc_project/state/host0', '/xyz/openbmc_project/logging'],
+    interfaces: [
+      'xyz.openbmc_project.State.Host',
+      'xyz.openbmc_project.Logging.Entry'
+    ]
   };
 
   const initWebSocket = () => {
@@ -23,11 +26,21 @@
       console.error(event);
     };
     ws.onmessage = event => {
-      const {
-        properties: { CurrentHostState, RequestedHostTransition } = {}
-      } = JSON.parse(event.data);
-      const hostState = CurrentHostState || RequestedHostTransition;
-      store.commit('global/setHostStatus', hostState);
+      const data = JSON.parse(event.data);
+      const eventInterface = data.interface;
+
+      if (eventInterface === 'xyz.openbmc_project.State.Host') {
+        const { properties: { CurrentHostState } = {} } = data;
+        store.commit('global/setHostStatus', CurrentHostState);
+      } else {
+        const { interfaces, event } = data;
+        if (event === 'InterfacesAdded' && interfaces) {
+          // Checking for 'InterfacesAdded' events
+          // since they have all properties needed to
+          // change health status
+          store.dispatch('eventLog/checkHealth', interfaces);
+        }
+      }
     };
   };
 
diff --git a/src/views/Overview/OverviewEvents.vue b/src/views/Overview/OverviewEvents.vue
index 5820e61..d15e158 100644
--- a/src/views/Overview/OverviewEvents.vue
+++ b/src/views/Overview/OverviewEvents.vue
@@ -13,7 +13,7 @@
         <p class="mb-1">{{ logData.eventID }}: {{ logData.description }}</p>
       </b-list-group-item>
     </b-list-group>
-    <b-list-group v-if="!eventLogData">
+    <b-list-group v-if="eventLogData.length === 0">
       There are no high priority events to display at this time.
     </b-list-group>
   </div>
@@ -28,7 +28,7 @@
   },
   computed: {
     eventLogData() {
-      return this.$store.getters['eventLog/eventLogData'];
+      return this.$store.getters['eventLog/highPriorityEvents'];
     }
   },
   created() {