diff options
author | Yoshie Muranaka <yoshiemuranaka@gmail.com> | 2020-02-07 00:47:28 +0300 |
---|---|---|
committer | Yoshie Muranaka <yoshiemuranaka@gmail.com> | 2020-02-17 21:48:43 +0300 |
commit | 1ace1d91e80425eeed482b33e21838acb7f7325a (patch) | |
tree | 86700b60471ae41222bdba6902b3cb308978953a /src | |
parent | c031b6988d6381ae1d2ecefd04e8c2af47f0ff4a (diff) | |
download | webui-vue-1ace1d91e80425eeed482b33e21838acb7f7325a.tar.xz |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/components/AppHeader/AppHeader.vue | 27 | ||||
-rw-r--r-- | src/components/Global/StatusIcon.vue | 9 | ||||
-rw-r--r-- | src/store/modules/GlobalStore.js | 2 | ||||
-rw-r--r-- | src/store/modules/Health/EventLogStore.js | 134 | ||||
-rw-r--r-- | src/store/plugins/WebSocketPlugin.js | 31 | ||||
-rw-r--r-- | src/views/Overview/OverviewEvents.vue | 4 |
6 files changed, 148 insertions, 59 deletions
diff --git a/src/components/AppHeader/AppHeader.vue b/src/components/AppHeader/AppHeader.vue index 880c428f..d411c1f1 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 @@ export default { hostStatus() { return this.$store.getters['global/hostStatus']; }, + healthStatus() { + return this.$store.getters['eventLog/healthStatus']; + }, hostStatusIcon() { switch (this.hostStatus) { case 'on': @@ -56,15 +59,31 @@ export default { 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 @@ export default { 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 a2c7f04f..d59eaec2 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 @@ export default { 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 @@ export default { &.secondary { fill: $secondary; } + &.warning { + fill: $warning; + } } </style> diff --git a/src/store/modules/GlobalStore.js b/src/store/modules/GlobalStore.js index 18d5043d..21ea796f 100644 --- a/src/store/modules/GlobalStore.js +++ b/src/store/modules/GlobalStore.js @@ -37,7 +37,7 @@ const GlobalStore = { 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 404a9639..3f32ab16 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 3e2139dd..409b1686 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 @@ const WebSocketPlugin = store => { 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 5820e612..d15e158f 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 @@ export default { }, computed: { eventLogData() { - return this.$store.getters['eventLog/eventLogData']; + return this.$store.getters['eventLog/highPriorityEvents']; } }, created() { |