summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSandeepa Singh <sandeepa.singh@ibm.com>2021-05-24 11:21:09 +0300
committerDerick Montague <derick.montague@ibm.com>2021-07-21 15:56:02 +0300
commit06d53863a83c003e7248f5cfc8362765882d19bb (patch)
tree2603dc1b60ac4b59c7ec6a16e43c5ec3b50e2a38
parentf65cc7b29a90b71a5e6c888ea180150e232be79e (diff)
downloadwebui-vue-06d53863a83c003e7248f5cfc8362765882d19bb.tar.xz
Add POST code logs page
This page will be included in the Health section of the primary navigation. The user will be able to export and download POST code logs. Signed-off-by: Sandeepa Singh <sandeepa.singh@ibm.com> Change-Id: I26cf1e01bfdfcf298f24f2c7dd9633ab7d31f1b5
-rw-r--r--src/components/AppNavigation/AppNavigationMixin.js5
-rw-r--r--src/components/Global/TableRowAction.vue16
-rw-r--r--src/env/components/AppNavigation/ibm.js5
-rw-r--r--src/env/router/ibm.js9
-rw-r--r--src/locales/en-US.json21
-rw-r--r--src/router/routes.js9
-rw-r--r--src/store/index.js2
-rw-r--r--src/store/modules/Health/PostCodeLogsStore.js39
-rw-r--r--src/views/Health/PostCodeLogs/PostCodeLogs.vue344
-rw-r--r--src/views/Health/PostCodeLogs/index.js2
10 files changed, 452 insertions, 0 deletions
diff --git a/src/components/AppNavigation/AppNavigationMixin.js b/src/components/AppNavigation/AppNavigationMixin.js
index 58852197..f9ba0776 100644
--- a/src/components/AppNavigation/AppNavigationMixin.js
+++ b/src/components/AppNavigation/AppNavigationMixin.js
@@ -39,6 +39,11 @@ const AppNavigationMixin = {
route: '/health/hardware-status',
},
{
+ id: 'post-code-logs',
+ label: this.$t('appNavigation.postCodeLogs'),
+ route: '/health/post-code-logs',
+ },
+ {
id: 'sensors',
label: this.$t('appNavigation.sensors'),
route: '/health/sensors',
diff --git a/src/components/Global/TableRowAction.vue b/src/components/Global/TableRowAction.vue
index 9d853bc7..99fa58b3 100644
--- a/src/components/Global/TableRowAction.vue
+++ b/src/components/Global/TableRowAction.vue
@@ -13,6 +13,18 @@
<span v-if="btnIconOnly" class="sr-only">{{ title }}</span>
</b-link>
<b-link
+ v-else-if="value === 'download' && downloadInNewTab"
+ class="align-bottom btn-icon-only py-0 btn-link"
+ target="_blank"
+ :href="downloadLocation"
+ :title="title"
+ >
+ <slot name="icon" />
+ <span class="sr-only">
+ {{ $t('global.action.download') }}
+ </span>
+ </b-link>
+ <b-link
v-else-if="value === 'download'"
class="align-bottom btn-icon-only py-0 btn-link"
:download="exportName"
@@ -74,6 +86,10 @@ export default {
type: Boolean,
default: true,
},
+ downloadInNewTab: {
+ type: Boolean,
+ default: false,
+ },
},
computed: {
dataForExport() {
diff --git a/src/env/components/AppNavigation/ibm.js b/src/env/components/AppNavigation/ibm.js
index d4b8e3dc..b8186a44 100644
--- a/src/env/components/AppNavigation/ibm.js
+++ b/src/env/components/AppNavigation/ibm.js
@@ -44,6 +44,11 @@ const AppNavigationMixin = {
route: '/health/hardware-status',
},
{
+ id: 'post-code-logs',
+ label: this.$t('appNavigation.postCodeLogs'),
+ route: '/health/post-code-logs',
+ },
+ {
id: 'sensors',
label: this.$t('appNavigation.sensors'),
route: '/health/sensors',
diff --git a/src/env/router/ibm.js b/src/env/router/ibm.js
index e0586e82..91b70a70 100644
--- a/src/env/router/ibm.js
+++ b/src/env/router/ibm.js
@@ -15,6 +15,7 @@ import ManagePowerUsage from '@/views/Control/ManagePowerUsage';
import NetworkSettings from '@/views/Configuration/NetworkSettings';
import Overview from '@/views/Overview';
import PageNotFound from '@/views/PageNotFound';
+import PostCodeLogs from '@/views/Health/PostCodeLogs';
import PowerRestorePolicy from '@/views/Control/PowerRestorePolicy';
import ProfileSettings from '@/views/ProfileSettings';
import RebootBmc from '@/views/Control/RebootBmc';
@@ -119,6 +120,14 @@ const routes = [
},
},
{
+ path: '/health/post-code-logs',
+ name: 'post-code-logs',
+ component: PostCodeLogs,
+ meta: {
+ title: i18n.t('appPageTitle.postCodeLogs'),
+ },
+ },
+ {
path: '/health/sensors',
name: 'sensors',
component: Sensors,
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index c9e6bc56..a98625bc 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -114,6 +114,7 @@
"networkSettings": "@:appPageTitle.networkSettings",
"overview": "@:appPageTitle.overview",
"primaryNavigation": "Primary navigation",
+ "postCodeLogs": "@:appPageTitle.postCodeLogs",
"powerRestorePolicy": "@:appPageTitle.powerRestorePolicy",
"rebootBmc": "@:appPageTitle.rebootBmc",
"securitySettings": "@:appPageTitle.securitySettings",
@@ -142,6 +143,7 @@
"networkSettings": "Network settings",
"overview": "Overview",
"pageNotFound": "Page not found",
+ "postCodeLogs": "POST code logs",
"powerRestorePolicy": "Power restore policy",
"profileSettings": "Profile settings",
"rebootBmc": "Reboot BMC",
@@ -587,6 +589,25 @@
"solConsole": "@:appNavigation.serialOverLan"
}
},
+ "pagePostCodeLogs":{
+ "allExportFilePrefix": "All_POST_codes_log_",
+ "downloadFilePrefix": "POST_codes_additional_details_",
+ "exportFilePrefix": "POST_codes_log_",
+ "action": {
+ "downloadDetails": "Download additional details",
+ "exportLogs": "Export log"
+ },
+ "button": {
+ "exportAll": "Export all"
+ },
+ "table": {
+ "created": "Created",
+ "bootCount": "Boot count",
+ "postCode": "POST code",
+ "searchLogs": "Search logs",
+ "timeStampOffset": "Time stamp offset"
+ }
+ },
"pageProfileSettings": {
"browserOffset": "Browser offset (%{timezone})",
"changePassword": "Change password",
diff --git a/src/router/routes.js b/src/router/routes.js
index e5812e00..b01b0da6 100644
--- a/src/router/routes.js
+++ b/src/router/routes.js
@@ -17,6 +17,7 @@ import ManagePowerUsage from '@/views/Control/ManagePowerUsage';
import NetworkSettings from '@/views/Configuration/NetworkSettings';
import Overview from '@/views/Overview';
import PageNotFound from '@/views/PageNotFound';
+import PostCodeLogs from '@/views/Health/PostCodeLogs';
import PowerRestorePolicy from '@/views/Control/PowerRestorePolicy';
import ProfileSettings from '@/views/ProfileSettings';
import RebootBmc from '@/views/Control/RebootBmc';
@@ -119,6 +120,14 @@ const routes = [
},
},
{
+ path: '/health/post-codes-logs',
+ name: 'post-codes-logs',
+ component: PostCodeLogs,
+ meta: {
+ title: i18n.t('appPageTitle.postCodeLogs'),
+ },
+ },
+ {
path: '/health/sensors',
name: 'sensors',
component: Sensors,
diff --git a/src/store/index.js b/src/store/index.js
index 82efab91..29dfe4fc 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -23,6 +23,7 @@ import FanStore from './modules/Health/FanStore';
import ChassisStore from './modules/Health/ChassisStore';
import BmcStore from './modules/Health/BmcStore';
import ProcessorStore from './modules/Health/ProcessorStore';
+import PostCodeLogsStore from './modules/Health/PostCodeLogsStore';
import SecuritySettingsStore from './modules/Configuration/SecuritySettingsStore';
import FactoryResetStore from './modules/Control/FactoryResetStore';
@@ -60,6 +61,7 @@ export default new Vuex.Store({
chassis: ChassisStore,
bmc: BmcStore,
processors: ProcessorStore,
+ postCodeLogs: PostCodeLogsStore,
virtualMedia: VirtualMediaStore,
securitySettings: SecuritySettingsStore,
factoryReset: FactoryResetStore,
diff --git a/src/store/modules/Health/PostCodeLogsStore.js b/src/store/modules/Health/PostCodeLogsStore.js
new file mode 100644
index 00000000..ac470ece
--- /dev/null
+++ b/src/store/modules/Health/PostCodeLogsStore.js
@@ -0,0 +1,39 @@
+import api from '@/store/api';
+
+const PostCodeLogsStore = {
+ namespaced: true,
+ state: {
+ allPostCodes: [],
+ },
+ getters: {
+ allPostCodes: (state) => state.allPostCodes,
+ },
+ mutations: {
+ setAllPostCodes: (state, allPostCodes) =>
+ (state.allPostCodes = allPostCodes),
+ },
+ actions: {
+ async getPostCodesLogData({ commit }) {
+ return await api
+ .get('/redfish/v1/Systems/system/LogServices/PostCodes/Entries')
+ .then(({ data: { Members = [] } = {} }) => {
+ const postCodeLogs = Members.map((log) => {
+ const { Created, MessageArgs, AdditionalDataURI } = log;
+ return {
+ date: new Date(Created),
+ bootCount: MessageArgs[0],
+ timeStampOffset: MessageArgs[1],
+ postCode: MessageArgs[2],
+ uri: AdditionalDataURI,
+ };
+ });
+ commit('setAllPostCodes', postCodeLogs);
+ })
+ .catch((error) => {
+ console.log('POST Codes Log Data:', error);
+ });
+ },
+ },
+};
+
+export default PostCodeLogsStore;
diff --git a/src/views/Health/PostCodeLogs/PostCodeLogs.vue b/src/views/Health/PostCodeLogs/PostCodeLogs.vue
new file mode 100644
index 00000000..1154cbff
--- /dev/null
+++ b/src/views/Health/PostCodeLogs/PostCodeLogs.vue
@@ -0,0 +1,344 @@
+<template>
+ <b-container fluid="xl">
+ <page-title />
+ <b-row class="align-items-start">
+ <b-col sm="8" xl="6" class="d-sm-flex align-items-end mb-4">
+ <search
+ :placeholder="$t('pagePostCodeLogs.table.searchLogs')"
+ @change-search="onChangeSearchInput"
+ @clear-search="onClearSearchInput"
+ />
+ <div class="ml-sm-4">
+ <table-cell-count
+ :filtered-items-count="filteredRows"
+ :total-number-of-cells="allLogs.length"
+ ></table-cell-count>
+ </div>
+ </b-col>
+ <b-col sm="8" md="7" xl="6">
+ <table-date-filter @change="onChangeDateTimeFilter" />
+ </b-col>
+ </b-row>
+ <b-row>
+ <b-col xl="12" class="text-right">
+ <b-button
+ variant="primary"
+ :disabled="allLogs.length === 0"
+ :download="exportFileNameByDate()"
+ :href="href"
+ >
+ <icon-export /> {{ $t('pagePostCodeLogs.button.exportAll') }}
+ </b-button>
+ </b-col>
+ </b-row>
+ <b-row>
+ <b-col>
+ <table-toolbar
+ ref="toolbar"
+ :selected-items-count="selectedRows.length"
+ @clear-selected="clearSelectedRows($refs.table)"
+ >
+ <template #export>
+ <table-toolbar-export
+ :data="batchExportData"
+ :file-name="exportFileNameByDate('export')"
+ />
+ </template>
+ </table-toolbar>
+ <b-table
+ id="table-post-code-logs"
+ ref="table"
+ responsive="md"
+ selectable
+ no-select-on-click
+ sort-icon-left
+ hover
+ no-sort-reset
+ sort-desc
+ show-empty
+ sort-by="id"
+ :fields="fields"
+ :items="filteredLogs"
+ :empty-text="$t('global.table.emptyMessage')"
+ :empty-filtered-text="$t('global.table.emptySearchMessage')"
+ :per-page="perPage"
+ :current-page="currentPage"
+ :filter="searchFilter"
+ @filtered="onFiltered"
+ @row-selected="onRowSelected($event, filteredLogs.length)"
+ >
+ <!-- Checkbox column -->
+ <template #head(checkbox)>
+ <b-form-checkbox
+ v-model="tableHeaderCheckboxModel"
+ data-test-id="postCode-checkbox-selectAll"
+ :indeterminate="tableHeaderCheckboxIndeterminate"
+ @change="onChangeHeaderCheckbox($refs.table)"
+ >
+ <span class="sr-only">{{ $t('global.table.selectAll') }}</span>
+ </b-form-checkbox>
+ </template>
+ <template #cell(checkbox)="row">
+ <b-form-checkbox
+ v-model="row.rowSelected"
+ :data-test-id="`postCode-checkbox-selectRow-${row.index}`"
+ @change="toggleSelectRow($refs.table, row.index)"
+ >
+ <span class="sr-only">{{ $t('global.table.selectItem') }}</span>
+ </b-form-checkbox>
+ </template>
+ <!-- Date column -->
+ <template #cell(date)="{ value }">
+ <p class="mb-0">{{ value | formatDate }}</p>
+ <p class="mb-0">{{ value | formatTime }}</p>
+ </template>
+
+ <!-- Actions column -->
+ <template #cell(actions)="row">
+ <table-row-action
+ v-for="(action, index) in row.item.actions"
+ :key="index"
+ :value="action.value"
+ :title="action.title"
+ :row-data="row.item"
+ :btn-icon-only="true"
+ :export-name="exportFileNameByDate(action.value)"
+ :download-location="row.item.uri"
+ :download-in-new-tab="true"
+ >
+ <template #icon>
+ <icon-export v-if="action.value === 'export'" />
+ <icon-download v-if="action.value === 'download'" />
+ </template>
+ </table-row-action>
+ </template>
+ </b-table>
+ </b-col>
+ </b-row>
+
+ <!-- Table pagination -->
+ <b-row>
+ <b-col sm="6">
+ <b-form-group
+ class="table-pagination-select"
+ :label="$t('global.table.itemsPerPage')"
+ label-for="pagination-items-per-page"
+ >
+ <b-form-select
+ id="pagination-items-per-page"
+ v-model="perPage"
+ :options="itemsPerPageOptions"
+ />
+ </b-form-group>
+ </b-col>
+ <b-col sm="6">
+ <b-pagination
+ v-model="currentPage"
+ first-number
+ last-number
+ :per-page="perPage"
+ :total-rows="getTotalRowCount(filteredLogs.length)"
+ aria-controls="table-post-code-logs"
+ />
+ </b-col>
+ </b-row>
+ </b-container>
+</template>
+
+<script>
+import IconDownload from '@carbon/icons-vue/es/download/20';
+import IconExport from '@carbon/icons-vue/es/document--export/20';
+import { omit } from 'lodash';
+import PageTitle from '@/components/Global/PageTitle';
+import Search from '@/components/Global/Search';
+import TableCellCount from '@/components/Global/TableCellCount';
+import TableDateFilter from '@/components/Global/TableDateFilter';
+import TableRowAction from '@/components/Global/TableRowAction';
+import TableToolbar from '@/components/Global/TableToolbar';
+import TableToolbarExport from '@/components/Global/TableToolbarExport';
+import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
+import TableFilterMixin from '@/components/Mixins/TableFilterMixin';
+import BVPaginationMixin, {
+ currentPage,
+ perPage,
+ itemsPerPageOptions,
+} from '@/components/Mixins/BVPaginationMixin';
+import BVTableSelectableMixin, {
+ selectedRows,
+ tableHeaderCheckboxModel,
+ tableHeaderCheckboxIndeterminate,
+} from '@/components/Mixins/BVTableSelectableMixin';
+import BVToastMixin from '@/components/Mixins/BVToastMixin';
+import TableDataFormatterMixin from '@/components/Mixins/TableDataFormatterMixin';
+import TableSortMixin from '@/components/Mixins/TableSortMixin';
+import TableRowExpandMixin, {
+ expandRowLabel,
+} from '@/components/Mixins/TableRowExpandMixin';
+import SearchFilterMixin, {
+ searchFilter,
+} from '@/components/Mixins/SearchFilterMixin';
+
+export default {
+ components: {
+ IconExport,
+ IconDownload,
+ PageTitle,
+ Search,
+ TableCellCount,
+ TableRowAction,
+ TableToolbar,
+ TableToolbarExport,
+ TableDateFilter,
+ },
+ mixins: [
+ BVPaginationMixin,
+ BVTableSelectableMixin,
+ BVToastMixin,
+ LoadingBarMixin,
+ TableFilterMixin,
+ TableDataFormatterMixin,
+ TableSortMixin,
+ TableRowExpandMixin,
+ SearchFilterMixin,
+ ],
+ beforeRouteLeave(to, from, next) {
+ // Hide loader if the user navigates to another page
+ // before request is fulfilled.
+ this.hideLoader();
+ next();
+ },
+ data() {
+ return {
+ fields: [
+ {
+ key: 'checkbox',
+ sortable: false,
+ },
+ {
+ key: 'date',
+ label: this.$t('pagePostCodeLogs.table.created'),
+ },
+ {
+ key: 'timeStampOffset',
+ label: this.$t('pagePostCodeLogs.table.timeStampOffset'),
+ },
+ {
+ key: 'bootCount',
+ label: this.$t('pagePostCodeLogs.table.bootCount'),
+ },
+ {
+ key: 'postCode',
+ label: this.$t('pagePostCodeLogs.table.postCode'),
+ },
+ {
+ key: 'actions',
+ label: '',
+ tdClass: 'text-right text-nowrap',
+ },
+ ],
+ expandRowLabel,
+ activeFilters: [],
+ currentPage: currentPage,
+ filterStartDate: null,
+ filterEndDate: null,
+ itemsPerPageOptions: itemsPerPageOptions,
+ perPage: perPage,
+ searchFilter: searchFilter,
+ searchTotalFilteredRows: 0,
+ selectedRows: selectedRows,
+ tableHeaderCheckboxModel: tableHeaderCheckboxModel,
+ tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate,
+ };
+ },
+ computed: {
+ href() {
+ return `data:text/json;charset=utf-8,${this.exportAllLogsString()}`;
+ },
+ filteredRows() {
+ return this.searchFilter
+ ? this.searchTotalFilteredRows
+ : this.filteredLogs.length;
+ },
+ allLogs() {
+ return this.$store.getters['postCodeLogs/allPostCodes'].map(
+ (postCodes) => {
+ return {
+ ...postCodes,
+ actions: [
+ {
+ value: 'export',
+ title: this.$t('pagePostCodeLogs.action.exportLogs'),
+ },
+ {
+ value: 'download',
+ title: this.$t('pagePostCodeLogs.action.downloadDetails'),
+ },
+ ],
+ };
+ }
+ );
+ },
+ batchExportData() {
+ return this.selectedRows.map((row) => omit(row, 'actions'));
+ },
+ filteredLogsByDate() {
+ return this.getFilteredTableDataByDate(
+ this.allLogs,
+ this.filterStartDate,
+ this.filterEndDate
+ );
+ },
+ filteredLogs() {
+ return this.getFilteredTableData(
+ this.filteredLogsByDate,
+ this.activeFilters
+ );
+ },
+ },
+ created() {
+ this.startLoader();
+ this.$store
+ .dispatch('postCodeLogs/getPostCodesLogData')
+ .finally(() => this.endLoader());
+ },
+ methods: {
+ exportAllLogsString() {
+ {
+ return this.$store.getters['postCodeLogs/allPostCodes'].map(
+ (postCodes) => {
+ const allLogsString = JSON.stringify(postCodes);
+ return allLogsString;
+ }
+ );
+ }
+ },
+ onFilterChange({ activeFilters }) {
+ this.activeFilters = activeFilters;
+ },
+ onChangeDateTimeFilter({ fromDate, toDate }) {
+ this.filterStartDate = fromDate;
+ this.filterEndDate = toDate;
+ },
+ onFiltered(filteredItems) {
+ this.searchTotalFilteredRows = filteredItems.length;
+ },
+ // Create export file name based on date and action
+ exportFileNameByDate(value) {
+ let date = new Date();
+ date =
+ date.toISOString().slice(0, 10) +
+ '_' +
+ date.toString().split(':').join('-').split(' ')[4];
+ let fileName;
+ if (value === 'download') {
+ fileName = this.$t('pagePostCodeLogs.downloadFilePrefix');
+ } else if (value === 'export') {
+ fileName = this.$t('pagePostCodeLogs.exportFilePrefix');
+ } else {
+ fileName = this.$t('pagePostCodeLogs.allExportFilePrefix');
+ }
+ return fileName + date;
+ },
+ },
+};
+</script>
diff --git a/src/views/Health/PostCodeLogs/index.js b/src/views/Health/PostCodeLogs/index.js
new file mode 100644
index 00000000..ab591124
--- /dev/null
+++ b/src/views/Health/PostCodeLogs/index.js
@@ -0,0 +1,2 @@
+import PostCodeLogs from './PostCodeLogs.vue';
+export default PostCodeLogs;