diff options
-rw-r--r-- | package-lock.json | 6 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | src/assets/styles/_table.scss | 4 | ||||
-rw-r--r-- | src/components/AppNavigation/AppNavigation.vue | 2 | ||||
-rw-r--r-- | src/locales/en-US.json | 11 | ||||
-rw-r--r-- | src/router/index.js | 7 | ||||
-rw-r--r-- | src/store/index.js | 4 | ||||
-rw-r--r-- | src/store/modules/Health/SensorsStore.js | 113 | ||||
-rw-r--r-- | src/views/Health/Sensors/Sensors.vue | 126 | ||||
-rw-r--r-- | src/views/Health/Sensors/index.js | 2 |
10 files changed, 271 insertions, 5 deletions
diff --git a/package-lock.json b/package-lock.json index 5222244d..bcd1bbcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5429,7 +5429,8 @@ "date-fns": { "version": "1.30.1", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true }, "de-indent": { "version": "1.0.2", @@ -10698,8 +10699,7 @@ "lodash": { "version": "4.17.15", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash._reinterpolate": { "version": "3.0.0", diff --git a/package.json b/package.json index 2d5f4d5e..b2a24237 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "bootstrap-vue": "2.5.0", "core-js": "3.3.2", "js-cookie": "^2.2.1", + "lodash": "4.17.15", "vue": "2.6.11", "vue-i18n": "8.15.3", "vue-router": "3.1.3", diff --git a/src/assets/styles/_table.scss b/src/assets/styles/_table.scss index 528cb805..2372d257 100644 --- a/src/assets/styles/_table.scss +++ b/src/assets/styles/_table.scss @@ -1,6 +1,10 @@ table { position: relative; z-index: $zindex-dropdown; + .status-icon svg { + width: 1rem; + height: auto; + } } .table-light { diff --git a/src/components/AppNavigation/AppNavigation.vue b/src/components/AppNavigation/AppNavigation.vue index 48b94c3d..d0fee43f 100644 --- a/src/components/AppNavigation/AppNavigation.vue +++ b/src/components/AppNavigation/AppNavigation.vue @@ -21,7 +21,7 @@ <b-nav-item href="javascript:void(0)"> {{ $t('appNavigation.hardwareStatus') }} </b-nav-item> - <b-nav-item href="javascript:void(0)"> + <b-nav-item to="/health/sensors"> {{ $t('appNavigation.sensors') }} </b-nav-item> </b-collapse> diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 63247da6..adc11850 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -180,6 +180,17 @@ "successRebootStart": "Rebooting BMC." } }, + "pageSensors": { + "table": { + "currentValue": "Current value", + "lowerWarning": "Lower warning", + "lowerCritical": "Lower critical", + "name": "Name", + "status": "Status", + "upperWarning": "Upper warning", + "upperCritical": "Upper critical" + } + }, "pageServerPowerOperations": { "currentStatus": "Current status", "hostname": "Hostname", diff --git a/src/router/index.js b/src/router/index.js index 0d246cda..cd6cf8bf 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -24,6 +24,13 @@ const routes = [ } }, { + path: '/health/sensors', + component: () => import('@/views/Health/Sensors'), + meta: { + title: 'appPageTitle.sensors' + } + }, + { path: '/access-control/local-user-management', name: 'local-users', component: () => import('@/views/AccessControl/LocalUserManagement'), diff --git a/src/store/index.js b/src/store/index.js index 27216990..08ada05e 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -11,6 +11,7 @@ import ControlStore from './modules/Control/ControlStore'; import PowerControlStore from './modules/Control/PowerControlStore'; import NetworkSettingStore from './modules/Configuration/NetworkSettingsStore'; import EventLogStore from './modules/Health/EventLogStore'; +import SensorsStore from './modules/Health/SensorsStore'; import WebSocketPlugin from './plugins/WebSocketPlugin'; @@ -30,7 +31,8 @@ export default new Vuex.Store({ controls: ControlStore, powerControl: PowerControlStore, networkSettings: NetworkSettingStore, - eventLog: EventLogStore + eventLog: EventLogStore, + sensors: SensorsStore }, plugins: [WebSocketPlugin] }); diff --git a/src/store/modules/Health/SensorsStore.js b/src/store/modules/Health/SensorsStore.js new file mode 100644 index 00000000..5da15156 --- /dev/null +++ b/src/store/modules/Health/SensorsStore.js @@ -0,0 +1,113 @@ +import api from '../../api'; +import { uniqBy } from 'lodash'; + +const SensorsStore = { + namespaced: true, + state: { + sensors: [] + }, + getters: { + sensors: state => state.sensors + }, + mutations: { + setSensors: (state, sensors) => { + state.sensors = uniqBy([...state.sensors, ...sensors], 'name'); + } + }, + actions: { + getAllSensors({ dispatch }) { + dispatch('getChassisCollection').then(collection => { + collection.forEach(item => { + dispatch('getSensors', item); + dispatch('getThermalSensors', item); + dispatch('getPowerSensors', item); + }); + }); + }, + getChassisCollection() { + return api + .get('/redfish/v1/Chassis') + .then(({ data: { Members } }) => + Members.map(member => member['@odata.id']) + ) + .catch(error => console.log(error)); + }, + getSensors({ commit }, id) { + api + .get(`${id}/Sensors`) + .then(({ data: { Members = [] } }) => { + const promises = Members.map(sensor => api.get(sensor['@odata.id'])); + api.all(promises).then( + api.spread((...responses) => { + const sensorData = responses.map(({ data }) => { + return { + name: data.Name, + status: data.Status.Health, + currentValue: data.Reading, + lowerCaution: data.Thresholds.LowerCaution.Reading, + upperCaution: data.Thresholds.UpperCaution.Reading, + lowerCritical: data.Thresholds.LowerCritical.Reading, + upperCritical: data.Thresholds.UpperCritical.Reading, + units: data.ReadingUnits + }; + }); + commit('setSensors', sensorData); + }) + ); + }) + .catch(error => console.log(error)); + }, + getThermalSensors({ commit }, id) { + api + .get(`${id}/Thermal`) + .then(({ data: { Fans = [], Temperatures = [] } }) => { + const sensorData = []; + Fans.forEach(sensor => { + sensorData.push({ + // TODO: add upper/lower threshold + name: sensor.Name, + status: sensor.Status.Health, + currentValue: sensor.Reading, + units: sensor.ReadingUnits + }); + }); + Temperatures.forEach(sensor => { + sensorData.push({ + name: sensor.Name, + status: sensor.Status.Health, + currentValue: sensor.ReadingCelsius, + lowerCaution: sensor.LowerThresholdNonCritical, + upperCaution: sensor.UpperThresholdNonCritical, + lowerCritical: sensor.LowerThresholdCritical, + upperCritical: sensor.UpperThresholdCritical, + units: '℃' + }); + }); + commit('setSensors', sensorData); + }) + .catch(error => console.log(error)); + }, + getPowerSensors({ commit }, id) { + api + .get(`${id}/Power`) + .then(({ data: { Voltages = [] } }) => { + const sensorData = Voltages.map(sensor => { + return { + name: sensor.Name, + status: sensor.Status.Health, + currentValue: sensor.ReadingVolts, + lowerCaution: sensor.LowerThresholdNonCritical, + upperCaution: sensor.UpperThresholdNonCritical, + lowerCritical: sensor.LowerThresholdCritical, + upperCritical: sensor.UpperThresholdCritical, + units: 'Volts' + }; + }); + commit('setSensors', sensorData); + }) + .catch(error => console.log(error)); + } + } +}; + +export default SensorsStore; diff --git a/src/views/Health/Sensors/Sensors.vue b/src/views/Health/Sensors/Sensors.vue new file mode 100644 index 00000000..70d4f90d --- /dev/null +++ b/src/views/Health/Sensors/Sensors.vue @@ -0,0 +1,126 @@ +<template> + <b-container fluid> + <page-title /> + <b-row> + <b-col xl="12"> + <b-table + sort-icon-left + no-sort-reset + sticky-header="75vh" + sort-by="status" + :items="allSensors" + :fields="fields" + :sort-desc="true" + :sort-compare="sortCompare" + > + <template v-slot:cell(status)="{ value }"> + <status-icon :status="statusIcon(value)" /> + {{ value }} + </template> + <template v-slot:cell(currentValue)="data"> + {{ data.value }} {{ data.item.units }} + </template> + <template v-slot:cell(lowerCaution)="data"> + {{ data.value }} {{ data.item.units }} + </template> + <template v-slot:cell(upperCaution)="data"> + {{ data.value }} {{ data.item.units }} + </template> + <template v-slot:cell(lowerCritical)="data"> + {{ data.value }} {{ data.item.units }} + </template> + <template v-slot:cell(upperCritical)="data"> + {{ data.value }} {{ data.item.units }} + </template> + </b-table> + </b-col> + </b-row> + </b-container> +</template> + +<script> +import PageTitle from '../../../components/Global/PageTitle'; +import StatusIcon from '../../../components/Global/StatusIcon'; + +const valueFormatter = value => { + if (value === null || value === undefined) { + return '--'; + } + return parseFloat(value.toFixed(3)); +}; + +export default { + name: 'Sensors', + components: { PageTitle, StatusIcon }, + data() { + return { + fields: [ + { + key: 'name', + sortable: true, + label: this.$t('pageSensors.table.name') + }, + { + key: 'status', + sortable: true, + label: this.$t('pageSensors.table.status') + }, + { + key: 'lowerCritical', + formatter: valueFormatter, + label: this.$t('pageSensors.table.lowerCritical') + }, + { + key: 'lowerCaution', + formatter: valueFormatter, + label: this.$t('pageSensors.table.lowerWarning') + }, + + { + key: 'currentValue', + formatter: valueFormatter, + label: this.$t('pageSensors.table.currentValue') + }, + { + key: 'upperCaution', + formatter: valueFormatter, + label: this.$t('pageSensors.table.upperWarning') + }, + { + key: 'upperCritical', + formatter: valueFormatter, + label: this.$t('pageSensors.table.upperCritical') + } + ] + }; + }, + computed: { + allSensors() { + return this.$store.getters['sensors/sensors']; + } + }, + created() { + this.$store.dispatch('sensors/getAllSensors'); + }, + methods: { + statusIcon(status) { + switch (status) { + case 'OK': + return 'success'; + case 'Warning': + return 'warning'; + case 'Critical': + return 'danger'; + default: + return ''; + } + }, + sortCompare(a, b, key) { + if (key === 'status') { + const status = ['OK', 'Warning', 'Critical']; + return status.indexOf(a.status) - status.indexOf(b.status); + } + } + } +}; +</script> diff --git a/src/views/Health/Sensors/index.js b/src/views/Health/Sensors/index.js new file mode 100644 index 00000000..fc71b611 --- /dev/null +++ b/src/views/Health/Sensors/index.js @@ -0,0 +1,2 @@ +import Sensors from './Sensors.vue'; +export default Sensors; |