diff options
author | Derick Montague <derick.montague@ibm.com> | 2021-06-28 23:52:22 +0300 |
---|---|---|
committer | Derick Montague <derick.montague@ibm.com> | 2021-08-10 22:20:42 +0300 |
commit | 828dda9b187684902710bb11621eca27bf0c6eec (patch) | |
tree | 322a9511f7df3317ae23ab55b08c9b3730dfd810 /src/views/Logs/Dumps | |
parent | 03dc2b7b9f0672aac84349fbc77aab55403447e0 (diff) | |
download | webui-vue-828dda9b187684902710bb11621eca27bf0c6eec.tar.xz |
IA update: Add logs and update overview icon
This is the first patchset of the information architecture changes.
These changes are the result of several months of design research with
users to improve the existing information architecture (IA). More
information can we found in the Github story.
This patchset will add a Logs section and move event logs and dumps from
the Health section to the new Logs section. It will also update the icon
used for the Overview page.
Github story: https://github.com/openbmc/webui-vue/issues/56
Testing:
1. IBM build:
- Logs contained Event logs and dumps
- Both pages rendered when clicking link
2. Intel build
- Logs contained Event logs only
- Logs page rendered when clicking link
3. Tested default build
- Logs contained Event logs only
- Logs page rendered when clicking link
Signed-off-by: Derick Montague <derick.montague@ibm.com>
Change-Id: I4621837202cf5ad3469d6ea460d9a5bdc79c8816
Diffstat (limited to 'src/views/Logs/Dumps')
-rw-r--r-- | src/views/Logs/Dumps/Dumps.vue | 337 | ||||
-rw-r--r-- | src/views/Logs/Dumps/DumpsForm.vue | 95 | ||||
-rw-r--r-- | src/views/Logs/Dumps/DumpsModalConfirmation.vue | 75 | ||||
-rw-r--r-- | src/views/Logs/Dumps/index.js | 2 |
4 files changed, 509 insertions, 0 deletions
diff --git a/src/views/Logs/Dumps/Dumps.vue b/src/views/Logs/Dumps/Dumps.vue new file mode 100644 index 00000000..4538b305 --- /dev/null +++ b/src/views/Logs/Dumps/Dumps.vue @@ -0,0 +1,337 @@ +<template> + <b-container fluid="xl"> + <page-title /> + <b-row> + <b-col sm="6" lg="5" xl="4"> + <page-section :section-title="$t('pageDumps.initiateDump')"> + <dumps-form /> + </page-section> + </b-col> + </b-row> + <b-row> + <b-col xl="10"> + <page-section :section-title="$t('pageDumps.dumpsAvailableOnBmc')"> + <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 + sort-desc + 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" + :download-location="row.item.data" + :export-name="exportFileName(row)" + @click-table-action="onTableRowAction($event, row.item)" + > + <template #icon> + <icon-download v-if="action.value === 'download'" /> + <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 IconDownload from '@carbon/icons-vue/es/download/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 BVToastMixin from '@/components/Mixins/BVToastMixin'; +import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; +import SearchFilterMixin, { + searchFilter, +} from '@/components/Mixins/SearchFilterMixin'; +import TableFilterMixin from '@/components/Mixins/TableFilterMixin'; + +export default { + components: { + DumpsForm, + IconDelete, + IconDownload, + PageSection, + PageTitle, + Search, + TableCellCount, + TableDateFilter, + TableRowAction, + TableToolbar, + }, + mixins: [ + BVTableSelectableMixin, + BVToastMixin, + 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/bmcDumps']; + }, + tableItems() { + return this.dumps.map((item) => { + return { + ...item, + actions: [ + { + value: 'download', + title: this.$t('global.action.download'), + }, + { + 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, dump) { + 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) { + this.$store + .dispatch('dumps/deleteDumps', [dump]) + .then((messages) => { + messages.forEach(({ type, message }) => { + if (type === 'success') { + this.successToast(message); + } else if (type === 'error') { + this.errorToast(message); + } + }); + }); + } + }); + } + }, + 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) { + if (this.selectedRows.length === this.dumps.length) { + this.$store + .dispatch('dumps/deleteAllDumps') + .then((success) => this.successToast(success)) + .catch(({ message }) => this.errorToast(message)); + } else { + this.$store + .dispatch('dumps/deleteDumps', this.selectedRows) + .then((messages) => { + messages.forEach(({ type, message }) => { + if (type === 'success') { + this.successToast(message); + } else if (type === 'error') { + this.errorToast(message); + } + }); + }); + } + } + }); + } + }, + exportFileName(row) { + let filename = row.item.dumpType + '_' + row.item.id + '.tar.gz'; + filename = filename.replace(RegExp(' ', 'g'), '_'); + return filename; + }, + }, +}; +</script> diff --git a/src/views/Logs/Dumps/DumpsForm.vue b/src/views/Logs/Dumps/DumpsForm.vue new file mode 100644 index 00000000..02ec1864 --- /dev/null +++ b/src/views/Logs/Dumps/DumpsForm.vue @@ -0,0 +1,95 @@ +<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> + <alert variant="info" class="mb-3" :show="selectedDumpType === 'system'"> + {{ $t('pageDumps.form.systemDumpInfo') }} + </alert> + <b-button variant="primary" type="submit" form="form-new-dump"> + {{ $t('pageDumps.form.initiateDump') }} + </b-button> + </b-form> + <modal-confirmation @ok="createSystemDump" /> + </div> +</template> + +<script> +import { required } from 'vuelidate/lib/validators'; + +import ModalConfirmation from './DumpsModalConfirmation'; +import Alert from '@/components/Global/Alert'; + +import BVToastMixin from '@/components/Mixins/BVToastMixin'; +import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; + +export default { + components: { Alert, ModalConfirmation }, + 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; + if (this.selectedDumpType === 'system') { + this.showConfirmationModal(); + } else { + this.$store + .dispatch('dumps/createBmcDump') + .then(() => + this.infoToast(this.$t('pageDumps.toast.successStartBmcDump'), { + title: this.$t('pageDumps.toast.successStartBmcDumpTitle'), + timestamp: true, + }) + ) + .catch(({ message }) => this.errorToast(message)); + } + }, + showConfirmationModal() { + this.$bvModal.show('modal-confirmation'); + }, + createSystemDump() { + this.$store + .dispatch('dumps/createSystemDump') + .then(() => + this.infoToast(this.$t('pageDumps.toast.successStartSystemDump'), { + title: this.$t('pageDumps.toast.successStartSystemDumpTitle'), + timestamp: true, + }) + ) + .catch(({ message }) => this.errorToast(message)); + }, + }, +}; +</script> diff --git a/src/views/Logs/Dumps/DumpsModalConfirmation.vue b/src/views/Logs/Dumps/DumpsModalConfirmation.vue new file mode 100644 index 00000000..f8e20cfd --- /dev/null +++ b/src/views/Logs/Dumps/DumpsModalConfirmation.vue @@ -0,0 +1,75 @@ +<template> + <b-modal + id="modal-confirmation" + ref="modal" + :title="$t('pageDumps.modal.initiateSystemDump')" + @hidden="resetForm" + > + <p> + <strong> + {{ $t('pageDumps.modal.initiateSystemDumpMessage1') }} + </strong> + </p> + <p> + {{ $t('pageDumps.modal.initiateSystemDumpMessage2') }} + </p> + <p> + <status-icon status="danger" /> + {{ $t('pageDumps.modal.initiateSystemDumpMessage3') }} + </p> + <b-form-checkbox v-model="confirmed" @input="$v.confirmed.$touch()"> + {{ $t('pageDumps.modal.initiateSystemDumpMessage4') }} + </b-form-checkbox> + <b-form-invalid-feedback + :state="getValidationState($v.confirmed)" + role="alert" + > + {{ $t('global.form.required') }} + </b-form-invalid-feedback> + <template #modal-footer="{ cancel }"> + <b-button variant="secondary" @click="cancel()"> + {{ $t('global.action.cancel') }} + </b-button> + <b-button variant="danger" @click="handleSubmit"> + {{ $t('pageDumps.form.initiateDump') }} + </b-button> + </template> + </b-modal> +</template> + +<script> +import StatusIcon from '@/components/Global/StatusIcon'; +import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js'; + +export default { + components: { StatusIcon }, + mixins: [VuelidateMixin], + data() { + return { + confirmed: false, + }; + }, + validations: { + confirmed: { + mustBeTrue: (value) => value === true, + }, + }, + methods: { + closeModal() { + this.$nextTick(() => { + this.$refs.modal.hide(); + }); + }, + handleSubmit() { + this.$v.$touch(); + if (this.$v.$invalid) return; + this.$emit('ok'); + this.closeModal(); + }, + resetForm() { + this.confirmed = false; + this.$v.$reset(); + }, + }, +}; +</script> diff --git a/src/views/Logs/Dumps/index.js b/src/views/Logs/Dumps/index.js new file mode 100644 index 00000000..65525fb0 --- /dev/null +++ b/src/views/Logs/Dumps/index.js @@ -0,0 +1,2 @@ +import Dumps from './Dumps.vue'; +export default Dumps; |