diff options
-rw-r--r-- | src/env/components/AppNavigation/ibm.js | 5 | ||||
-rw-r--r-- | src/env/components/Dumps/Dumps.vue | 289 | ||||
-rw-r--r-- | src/env/components/Dumps/DumpsForm.vue | 61 | ||||
-rw-r--r-- | src/env/components/Dumps/index.js | 2 | ||||
-rw-r--r-- | src/env/router/ibm.js | 9 | ||||
-rw-r--r-- | src/env/store/Dumps/DumpsStore.js | 32 | ||||
-rw-r--r-- | src/env/store/ibm.js | 2 | ||||
-rw-r--r-- | src/locales/en-US.json | 27 |
8 files changed, 427 insertions, 0 deletions
diff --git a/src/env/components/AppNavigation/ibm.js b/src/env/components/AppNavigation/ibm.js index a9870d3b..37231f93 100644 --- a/src/env/components/AppNavigation/ibm.js +++ b/src/env/components/AppNavigation/ibm.js @@ -29,6 +29,11 @@ const AppNavigationMixin = { icon: 'iconHealth', children: [ { + id: 'dumps', + label: this.$t('appNavigation.dumps'), + route: '/health/dumps', + }, + { id: 'event-logs', label: this.$t('appNavigation.eventLogs'), route: '/health/event-logs', diff --git a/src/env/components/Dumps/Dumps.vue b/src/env/components/Dumps/Dumps.vue new file mode 100644 index 00000000..eba90b7a --- /dev/null +++ b/src/env/components/Dumps/Dumps.vue @@ -0,0 +1,289 @@ +<template> + <b-container fluid="xl"> + <page-title /> + <b-row> + <b-col sm="6" lg="5" xl="4"> + <page-section :section-title="$t('pageDumps.newDump')"> + <dumps-form /> + </page-section> + </b-col> + </b-row> + <b-row> + <b-col xl="10"> + <page-section :section-title="$t('pageDumps.dumpHistory')"> + <b-row class="align-items-start"> + <b-col sm="8" xl="6" class="d-sm-flex align-items-end"> + <search + :placeholder="$t('pageDumps.table.searchDumps')" + @change-search="onChangeSearchInput" + @clear-search="onClearSearchInput" + /> + <div class="ml-sm-4"> + <table-cell-count + :filtered-items-count="filteredItemCount" + :total-number-of-cells="tableItems.length" + ></table-cell-count> + </div> + </b-col> + <b-col sm="8" md="7" xl="6"> + <table-date-filter @change="onChangeDateTimeFilter" /> + </b-col> + </b-row> + <table-toolbar + :selected-items-count="selectedRows.length" + :actions="batchActions" + @clear-selected="clearSelectedRows($refs.table)" + @batch-action="onTableBatchAction" + /> + <b-table + ref="table" + show-empty + hover + sort-icon-left + no-sort-reset + selectable + no-select-on-click + responsive="md" + sort-by="dateTime" + :fields="fields" + :items="filteredTableItems" + :empty-text="$t('global.table.emptyMessage')" + :empty-filtered-text="$t('global.table.emptySearchMessage')" + :filter="searchFilter" + @filtered="onChangeSearchFilter" + @row-selected="onRowSelected($event, filteredTableItems.length)" + > + <!-- Checkbox column --> + <template #head(checkbox)> + <b-form-checkbox + v-model="tableHeaderCheckboxModel" + :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" + @change="toggleSelectRow($refs.table, row.index)" + > + <span class="sr-only">{{ $t('global.table.selectItem') }}</span> + </b-form-checkbox> + </template> + + <!-- Date and Time column --> + <template #cell(dateTime)="{ value }"> + <p class="mb-0">{{ value | formatDate }}</p> + <p class="mb-0">{{ value | formatTime }}</p> + </template> + + <!-- Size column --> + <template #cell(size)="{ value }"> + {{ convertBytesToMegabytes(value) }} MB + </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" + @click-table-action="onTableRowAction($event, row.item)" + > + <template #icon> + <icon-delete v-if="action.value === 'delete'" /> + </template> + </table-row-action> + </template> + </b-table> + </page-section> + </b-col> + </b-row> + </b-container> +</template> + +<script> +import IconDelete from '@carbon/icons-vue/es/trash-can/20'; + +import DumpsForm from './DumpsForm'; +import PageSection from '@/components/Global/PageSection'; +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 BVTableSelectableMixin, { + selectedRows, + tableHeaderCheckboxModel, + tableHeaderCheckboxIndeterminate, +} from '@/components/Mixins/BVTableSelectableMixin'; +import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; +import SearchFilterMixin, { + searchFilter, +} from '@/components/Mixins/SearchFilterMixin'; +import TableFilterMixin from '@/components/Mixins/TableFilterMixin'; + +export default { + components: { + DumpsForm, + IconDelete, + PageSection, + PageTitle, + Search, + TableCellCount, + TableDateFilter, + TableRowAction, + TableToolbar, + }, + mixins: [ + BVTableSelectableMixin, + LoadingBarMixin, + SearchFilterMixin, + TableFilterMixin, + ], + 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: 'dateTime', + label: this.$t('pageDumps.table.dateAndTime'), + sortable: true, + }, + { + key: 'dumpType', + label: this.$t('pageDumps.table.dumpType'), + sortable: true, + }, + { + key: 'id', + label: this.$t('pageDumps.table.id'), + sortable: true, + }, + { + key: 'size', + label: this.$t('pageDumps.table.size'), + sortable: true, + }, + { + key: 'actions', + sortable: false, + label: '', + tdClass: 'text-right text-nowrap', + }, + ], + batchActions: [ + { + value: 'delete', + label: this.$t('global.action.delete'), + }, + ], + filterEndDate: null, + filterStartDate: null, + searchFilter, + searchFilteredItemsCount: 0, + selectedRows, + tableHeaderCheckboxIndeterminate, + tableHeaderCheckboxModel, + }; + }, + computed: { + dumps() { + return this.$store.getters['dumps/allDumps']; + }, + tableItems() { + return this.dumps.map((item) => { + return { + ...item, + actions: [ + { + value: 'delete', + title: this.$t('global.action.delete'), + }, + ], + }; + }); + }, + filteredTableItems() { + return this.getFilteredTableDataByDate( + this.tableItems, + this.filterStartDate, + this.filterEndDate, + 'dateTime' + ); + }, + filteredItemCount() { + return this.searchFilter + ? this.searchFilteredItemsCount + : this.filteredTableItems.length; + }, + }, + created() { + this.startLoader(); + this.$store.dispatch('dumps/getBmcDumps').finally(() => this.endLoader()); + }, + methods: { + convertBytesToMegabytes(bytes) { + return parseFloat((bytes / 1000000).toFixed(3)); + }, + onChangeSearchFilter(items) { + this.searchFilteredItemsCount = items.length; + }, + onChangeDateTimeFilter({ fromDate, toDate }) { + this.filterStartDate = fromDate; + this.filterEndDate = toDate; + }, + onTableRowAction(action) { + if (action === 'delete') { + this.$bvModal + .msgBoxConfirm(this.$tc('pageDumps.modal.deleteDumpConfirmation'), { + title: this.$tc('pageDumps.modal.deleteDump'), + okTitle: this.$tc('pageDumps.modal.deleteDump'), + cancelTitle: this.$t('global.action.cancel'), + }) + .then((deleteConfrimed) => { + if (deleteConfrimed); // delete dump + }); + } + }, + onTableBatchAction(action) { + if (action === 'delete') { + this.$bvModal + .msgBoxConfirm( + this.$tc( + 'pageDumps.modal.deleteDumpConfirmation', + this.selectedRows.length + ), + { + title: this.$tc( + 'pageDumps.modal.deleteDump', + this.selectedRows.length + ), + okTitle: this.$tc( + 'pageDumps.modal.deleteDump', + this.selectedRows.length + ), + cancelTitle: this.$t('global.action.cancel'), + } + ) + .then((deleteConfrimed) => { + if (deleteConfrimed); // delete dump + }); + } + }, + }, +}; +</script> diff --git a/src/env/components/Dumps/DumpsForm.vue b/src/env/components/Dumps/DumpsForm.vue new file mode 100644 index 00000000..ed81b3a8 --- /dev/null +++ b/src/env/components/Dumps/DumpsForm.vue @@ -0,0 +1,61 @@ +<template> + <div class="form-background p-3"> + <b-form id="form-new-dump" novalidate @submit.prevent="handleSubmit"> + <b-form-group + :label="$t('pageDumps.form.selectDumpType')" + label-for="selectDumpType" + > + <b-form-select + id="selectDumpType" + v-model="selectedDumpType" + :options="dumpTypeOptions" + :state="getValidationState($v.selectedDumpType)" + > + <template #first> + <b-form-select-option :value="null" disabled> + {{ $t('global.form.selectAnOption') }} + </b-form-select-option> + </template> + </b-form-select> + <b-form-invalid-feedback role="alert"> + {{ $t('global.form.required') }} + </b-form-invalid-feedback> + </b-form-group> + <b-button variant="primary" type="submit" form="form-new-dump"> + {{ $t('pageDumps.form.createNewDump') }} + </b-button> + </b-form> + </div> +</template> + +<script> +import { required } from 'vuelidate/lib/validators'; + +import BVToastMixin from '@/components/Mixins/BVToastMixin'; +import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; + +export default { + mixins: [BVToastMixin, VuelidateMixin], + data() { + return { + selectedDumpType: null, + dumpTypeOptions: [ + { value: 'bmc', text: this.$t('pageDumps.form.bmcDump') }, + { value: 'system', text: this.$t('pageDumps.form.systemDump') }, + ], + }; + }, + validations() { + return { + selectedDumpType: { required }, + }; + }, + methods: { + handleSubmit() { + this.$v.$touch(); + if (this.$v.$invalid) return; + this.successToast(this.$t('pageDumps.toast.successStartDump')); + }, + }, +}; +</script> diff --git a/src/env/components/Dumps/index.js b/src/env/components/Dumps/index.js new file mode 100644 index 00000000..65525fb0 --- /dev/null +++ b/src/env/components/Dumps/index.js @@ -0,0 +1,2 @@ +import Dumps from './Dumps.vue'; +export default Dumps; diff --git a/src/env/router/ibm.js b/src/env/router/ibm.js index a6473720..d4a6de0c 100644 --- a/src/env/router/ibm.js +++ b/src/env/router/ibm.js @@ -24,6 +24,7 @@ import i18n from '@/i18n'; // Custom components import FirmwareSingleImage from '../components/FirmwareSingleImage'; +import Dumps from '../components/Dumps'; const routes = [ { @@ -90,6 +91,14 @@ const routes = [ }, }, { + path: '/health/dumps', + name: 'dumps', + component: Dumps, + meta: { + title: i18n.t('appPageTitle.dumps'), + }, + }, + { path: '/health/event-logs', name: 'event-logs', component: EventLogs, diff --git a/src/env/store/Dumps/DumpsStore.js b/src/env/store/Dumps/DumpsStore.js new file mode 100644 index 00000000..45f446c0 --- /dev/null +++ b/src/env/store/Dumps/DumpsStore.js @@ -0,0 +1,32 @@ +import api from '@/store/api'; + +const DumpsStore = { + namespaced: true, + state: { + bmcDumps: [], + }, + getters: { + allDumps: (state) => state.bmcDumps, + }, + mutations: { + setBmcDumps: (state, dumps) => { + state.bmcDumps = dumps.map((dump) => ({ + dateTime: new Date(dump.Created), + dumpType: dump.Name, + id: dump.Id, + size: dump.AdditionalDataSizeBytes, + data: dump.AdditionalDataURI, + })); + }, + }, + actions: { + async getBmcDumps({ commit }) { + return await api + .get('/redfish/v1/Managers/bmc/LogServices/Dump/Entries') + .then(({ data = {} }) => commit('setBmcDumps', data.Members || [])) + .catch((error) => console.log(error)); + }, + }, +}; + +export default DumpsStore; diff --git a/src/env/store/ibm.js b/src/env/store/ibm.js index 383d97c7..e19dd48c 100644 --- a/src/env/store/ibm.js +++ b/src/env/store/ibm.js @@ -1,9 +1,11 @@ import store from '@/store'; import FirmwareSingleImageStore from './FirmwareSingleImage/FirmwareSingleImageStore'; +import DumpsStore from './Dumps/DumpsStore'; store.unregisterModule('virtualMedia'); store.unregisterModule('firmware'); store.registerModule('firmwareSingleImage', FirmwareSingleImageStore); +store.registerModule('dumps', DumpsStore); export default store; diff --git a/src/locales/en-US.json b/src/locales/en-US.json index dcb52a68..1d564ca6 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -99,6 +99,7 @@ "configuration": "Configuration", "control": "Control", "dateTimeSettings": "@:appPageTitle.dateTimeSettings", + "dumps": "@:appPageTitle.dumps", "eventLogs": "@:appPageTitle.eventLogs", "firmware": "@:appPageTitle.firmware", "hardwareStatus": "@:appPageTitle.hardwareStatus", @@ -124,6 +125,7 @@ "changePassword": "Change password", "clientSessions": "Client sessions", "dateTimeSettings": "Date and time settings", + "dumps": "Dumps", "eventLogs": "Event logs", "firmware": "Firmware", "hardwareStatus": "Hardware status", @@ -198,6 +200,31 @@ "successSaveDateTimeSettings": "Successfully saved date and time settings." } }, + "pageDumps": { + "dumpHistory": "Dump history", + "newDump": "New dump", + "form": { + "bmcDump": "BMC dump", + "createNewDump": "Create new dump", + "selectDumpType": "Select dump type", + "systemDump": "System dump (disruptive)" + }, + "modal": { + "deleteDump": "Delete dump | Delete dumps", + "deleteDumpConfirmation": "Are you sure you want to delete %{count} dump? This action cannot be undone. | Are you sure you want to delete %{count} dumps? This action cannot be undone." + }, + "table": { + "createdBy": "Created by", + "dateAndTime": "Date and time", + "dumpType": "Dump type", + "id": "ID", + "searchDumps": "Search dumps", + "size": "Size" + }, + "toast": { + "successStartDump": "Successfully started new dump." + } + }, "pageEventLogs": { "exportFilePrefix": "event_logs_", "modal": { |