diff options
-rw-r--r-- | src/components/Global/TableRowAction.vue | 66 | ||||
-rw-r--r-- | src/locales/en-US.json | 8 | ||||
-rw-r--r-- | src/store/api.js | 15 | ||||
-rw-r--r-- | src/store/modules/Health/EventLogStore.js | 63 | ||||
-rw-r--r-- | src/views/Health/EventLogs/EventLogs.vue | 164 |
5 files changed, 280 insertions, 36 deletions
diff --git a/src/components/Global/TableRowAction.vue b/src/components/Global/TableRowAction.vue index d41fd50c..f86bce22 100644 --- a/src/components/Global/TableRowAction.vue +++ b/src/components/Global/TableRowAction.vue @@ -1,18 +1,36 @@ <template> - <b-button - :aria-label="title" - :title="title" - variant="link" - :disabled="!enabled" - @click="$emit('click:tableAction', value)" - > - <slot name="icon"> - {{ title }} - </slot> - </b-button> + <span> + <b-link + v-if="value === 'export'" + class="align-bottom btn-link py-0" + :download="download" + :href="href" + :title="title" + :aria-label="title" + > + <slot name="icon"> + {{ $t('global.action.export') }} + </slot> + </b-link> + <b-button + v-else + variant="link" + class="py-0" + :aria-label="title" + :title="title" + :disabled="!enabled" + @click="$emit('click:tableAction', value)" + > + <slot name="icon"> + {{ title }} + </slot> + </b-button> + </span> </template> <script> +import { omit } from 'lodash'; + export default { name: 'TableRowAction', props: { @@ -27,14 +45,26 @@ export default { title: { type: String, default: null + }, + rowData: { + type: Object, + default: () => {} + }, + exportName: { + type: String, + default: 'export' + } + }, + computed: { + dataForExport() { + return JSON.stringify(omit(this.rowData, 'actions')); + }, + download() { + return `${this.exportName}.json`; + }, + href() { + return `data:text/json;charset=utf-8,${this.dataForExport}`; } } }; </script> - -<style lang="scss" scoped> -.btn.btn-link { - padding-top: 0; - padding-bottom: 0; -} -</style> diff --git a/src/locales/en-US.json b/src/locales/en-US.json index a09047e9..4636503b 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -98,6 +98,10 @@ "unauthorized": "Unauthorized" }, "pageEventLogs": { + "modal": { + "deleteTitle": "Delete log | Delete logs", + "deleteMessage": "Are you sure you want to delete %{count} log? This action cannot be undone. | Are you sure you want to delete %{count} logs? This action cannot be undone." + }, "table": { "date": "Date", "description": "Description", @@ -105,6 +109,10 @@ "id": "ID", "severity": "Severity", "type": "Type" + }, + "toast": { + "errorDelete": "Error deleting %{count} log. | Error deleting %{count} logs.", + "successDelete": "Successfully deleted %{count} log. | Successfully deleted %{count} logs." } }, "pageLdap": { diff --git a/src/store/api.js b/src/store/api.js index 4a8b8e80..63fd75cb 100644 --- a/src/store/api.js +++ b/src/store/api.js @@ -55,3 +55,18 @@ export default { return Axios.spread(callback); } }; + +export const getResponseCount = responses => { + let successCount = 0; + let errorCount = 0; + + responses.forEach(response => { + if (response instanceof Error) errorCount++; + else successCount++; + }); + + return { + successCount, + errorCount + }; +}; diff --git a/src/store/modules/Health/EventLogStore.js b/src/store/modules/Health/EventLogStore.js index 2f0b800f..2b93ffa7 100644 --- a/src/store/modules/Health/EventLogStore.js +++ b/src/store/modules/Health/EventLogStore.js @@ -1,4 +1,5 @@ -import api from '../../api'; +import api, { getResponseCount } from '@/store/api'; +import i18n from '@/i18n'; const getHealthStatus = events => { let status = 'OK'; @@ -37,22 +38,60 @@ const EventLogStore = { return await api .get('/redfish/v1/Systems/system/LogServices/EventLog/Entries') .then(({ data: { Members = [] } = {} }) => { - const eventLogs = Members.map( - ({ Id, Severity, Created, EntryType, Message }) => { - return { - id: Id, - severity: Severity, - date: new Date(Created), - type: EntryType, - description: Message - }; - } - ); + const eventLogs = Members.map(log => { + const { Id, Severity, Created, EntryType, Message } = log; + return { + id: Id, + severity: Severity, + date: new Date(Created), + type: EntryType, + description: Message, + uri: log['@odata.id'] + }; + }); commit('setAllEvents', eventLogs); }) .catch(error => { console.log('Event Log Data:', error); }); + }, + async deleteEventLogs({ dispatch }, uris = []) { + const promises = uris.map(uri => + api.delete(uri).catch(error => { + console.log(error); + return error; + }) + ); + return await api + .all(promises) + .then(response => { + dispatch('getEventLogData'); + return response; + }) + .then( + api.spread((...responses) => { + const { successCount, errorCount } = getResponseCount(responses); + const toastMessages = []; + + if (successCount) { + const message = i18n.tc( + 'pageEventLogs.toast.successDelete', + successCount + ); + toastMessages.push({ type: 'success', message }); + } + + if (errorCount) { + const message = i18n.tc( + 'pageEventLogs.toast.errorDelete', + errorCount + ); + toastMessages.push({ type: 'error', message }); + } + + return toastMessages; + }) + ); } } }; diff --git a/src/views/Health/EventLogs/EventLogs.vue b/src/views/Health/EventLogs/EventLogs.vue index d7a64c90..a5ef3757 100644 --- a/src/views/Health/EventLogs/EventLogs.vue +++ b/src/views/Health/EventLogs/EventLogs.vue @@ -8,27 +8,81 @@ </b-row> <b-row> <b-col> + <table-toolbar + ref="toolbar" + :selected-items-count="selectedRows.length" + :actions="batchActions" + @clearSelected="clearSelectedRows($refs.table)" + @batchAction="onBatchAction" + > + <template v-slot:export> + <table-toolbar-export + :data="batchExportData" + :file-name="$t('appPageTitle.eventLogs')" + /> + </template> + </table-toolbar> <b-table id="table-event-logs" - :fields="fields" - :items="filteredLogs" + ref="table" + selectable + no-select-on-click sort-icon-left no-sort-reset sort-desc show-empty sort-by="date" + :fields="fields" + :items="filteredLogs" :sort-compare="onSortCompare" :empty-text="$t('pageEventLogs.table.emptyMessage')" :per-page="perPage" :current-page="currentPage" + @row-selected="onRowSelected($event, filteredLogs.length)" > + <!-- Checkbox column --> + <template v-slot:head(checkbox)> + <b-form-checkbox + v-model="tableHeaderCheckboxModel" + :indeterminate="tableHeaderCheckboxIndeterminate" + @change="onChangeHeaderCheckbox($refs.table)" + /> + </template> + <template v-slot:cell(checkbox)="row"> + <b-form-checkbox + v-model="row.rowSelected" + @change="toggleSelectRow($refs.table, row.index)" + /> + </template> + + <!-- Severity column --> <template v-slot:cell(severity)="{ value }"> <status-icon :status="getStatus(value)" /> {{ value }} </template> + + <!-- Date column --> <template v-slot:cell(date)="{ value }"> {{ value | formatDate }} {{ value | formatTime }} </template> + + <!-- Actions column --> + <template v-slot:cell(actions)="{ item }"> + <table-row-action + v-for="(action, index) in item.actions" + :key="index" + :value="action.value" + :title="action.title" + :row-data="item" + :export-name="item.id" + @click:tableAction="onTableRowAction($event, item)" + > + <template v-slot:icon> + <icon-export v-if="action.value === 'export'" /> + <icon-trashcan v-if="action.value === 'delete'" /> + </template> + </table-row-action> + </template> </b-table> </b-col> </b-row> @@ -61,27 +115,51 @@ </template> <script> +import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; +import IconExport from '@carbon/icons-vue/es/export/20'; +import { omit } from 'lodash'; + import PageTitle from '@/components/Global/PageTitle'; import StatusIcon from '@/components/Global/StatusIcon'; import TableFilter from '@/components/Global/TableFilter'; +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 from '@/components/Mixins/BVPaginationMixin'; +import BVTableSelectableMixin from '@/components/Mixins/BVTableSelectableMixin'; +import BVToastMixin from '@/components/Mixins/BVToastMixin'; const SEVERITY = ['OK', 'Warning', 'Critical']; export default { components: { + IconExport, + IconTrashcan, PageTitle, StatusIcon, - TableFilter + TableFilter, + TableRowAction, + TableToolbar, + TableToolbarExport }, - mixins: [LoadingBarMixin, TableFilterMixin, BVPaginationMixin], + mixins: [ + BVPaginationMixin, + BVTableSelectableMixin, + BVToastMixin, + LoadingBarMixin, + TableFilterMixin + ], data() { return { fields: [ { + key: 'checkbox', + sortable: false + }, + { key: 'id', label: this.$t('pageEventLogs.table.id'), sortable: true @@ -104,6 +182,12 @@ export default { { key: 'description', label: this.$t('pageEventLogs.table.description') + }, + { + key: 'actions', + sortable: false, + label: '', + tdClass: 'text-right' } ], tableFilters: [ @@ -112,12 +196,32 @@ export default { values: SEVERITY } ], - activeFilters: [] + activeFilters: [], + batchActions: [ + { + value: 'delete', + label: this.$t('global.action.delete') + } + ] }; }, computed: { allLogs() { - return this.$store.getters['eventLog/allEvents']; + return this.$store.getters['eventLog/allEvents'].map(event => { + return { + ...event, + actions: [ + { + value: 'export', + title: this.$t('global.action.export') + }, + { + value: 'delete', + title: this.$t('global.action.delete') + } + ] + }; + }); }, filteredLogs: { get: function() { @@ -126,6 +230,9 @@ export default { set: function(newVal) { return newVal; } + }, + batchExportData() { + return this.selectedRows.map(row => omit(row, 'actions')); } }, created() { @@ -153,6 +260,17 @@ export default { return ''; } }, + deleteLogs(uris) { + this.$store.dispatch('eventLog/deleteEventLogs', uris).then(messages => { + messages.forEach(({ type, message }) => { + if (type === 'success') { + this.successToast(message); + } else if (type === 'error') { + this.errorToast(message); + } + }); + }); + }, onFilterChange({ activeFilters }) { this.activeFilters = activeFilters; this.filteredLogs = this.getFilteredTableData( @@ -164,6 +282,40 @@ export default { if (key === 'severity') { return SEVERITY.indexOf(a.status) - SEVERITY.indexOf(b.status); } + }, + onTableRowAction(action, { uri }) { + if (action === 'delete') { + this.$bvModal + .msgBoxConfirm(this.$tc('pageEventLogs.modal.deleteMessage'), { + title: this.$tc('pageEventLogs.modal.deleteTitle'), + okTitle: this.$t('global.action.delete') + }) + .then(deleteConfirmed => { + if (deleteConfirmed) this.deleteLogs([uri]); + }); + } + }, + onBatchAction(action) { + if (action === 'delete') { + const uris = this.selectedRows.map(row => row.uri); + this.$bvModal + .msgBoxConfirm( + this.$tc( + 'pageEventLogs.modal.deleteMessage', + this.selectedRows.length + ), + { + title: this.$tc( + 'pageEventLogs.modal.deleteTitle', + this.selectedRows.length + ), + okTitle: this.$t('global.action.delete') + } + ) + .then(deleteConfirmed => { + if (deleteConfirmed) this.deleteLogs(uris); + }); + } } } }; |