summaryrefslogtreecommitdiff
path: root/src/views/Logs
diff options
context:
space:
mode:
authorDerick Montague <derick.montague@ibm.com>2021-06-28 23:52:22 +0300
committerDerick Montague <derick.montague@ibm.com>2021-08-10 22:20:42 +0300
commit828dda9b187684902710bb11621eca27bf0c6eec (patch)
tree322a9511f7df3317ae23ab55b08c9b3730dfd810 /src/views/Logs
parent03dc2b7b9f0672aac84349fbc77aab55403447e0 (diff)
downloadwebui-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')
-rw-r--r--src/views/Logs/Dumps/Dumps.vue337
-rw-r--r--src/views/Logs/Dumps/DumpsForm.vue95
-rw-r--r--src/views/Logs/Dumps/DumpsModalConfirmation.vue75
-rw-r--r--src/views/Logs/Dumps/index.js2
-rw-r--r--src/views/Logs/EventLogs/EventLogs.vue597
-rw-r--r--src/views/Logs/EventLogs/index.js2
6 files changed, 1108 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;
diff --git a/src/views/Logs/EventLogs/EventLogs.vue b/src/views/Logs/EventLogs/EventLogs.vue
new file mode 100644
index 00000000..fa3f43cb
--- /dev/null
+++ b/src/views/Logs/EventLogs/EventLogs.vue
@@ -0,0 +1,597 @@
+<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('pageEventLogs.table.searchLogs')"
+ data-test-id="eventLogs-input-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 class="text-right">
+ <table-filter :filters="tableFilters" @filter-change="onFilterChange" />
+ <b-button
+ variant="link"
+ :disabled="allLogs.length === 0"
+ @click="deleteAllLogs"
+ >
+ <icon-delete /> {{ $t('global.action.deleteAll') }}
+ </b-button>
+ <b-button
+ variant="primary"
+ :class="{ disabled: allLogs.length === 0 }"
+ :download="exportFileNameByDate()"
+ :href="href"
+ >
+ <icon-export /> {{ $t('global.action.exportAll') }}
+ </b-button>
+ </b-col>
+ </b-row>
+ <b-row>
+ <b-col>
+ <table-toolbar
+ ref="toolbar"
+ :selected-items-count="selectedRows.length"
+ :actions="batchActions"
+ @clear-selected="clearSelectedRows($refs.table)"
+ @batch-action="onBatchAction"
+ >
+ <template #toolbar-buttons>
+ <b-button variant="primary" @click="resolveLogs">
+ {{ $t('pageEventLogs.resolve') }}
+ </b-button>
+ <b-button variant="primary" @click="unresolveLogs">
+ {{ $t('pageEventLogs.unresolve') }}
+ </b-button>
+ <table-toolbar-export
+ :data="batchExportData"
+ :file-name="exportFileNameByDate()"
+ />
+ </template>
+ </table-toolbar>
+ <b-table
+ id="table-event-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"
+ :sort-compare="onSortCompare"
+ :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="eventLogs-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="`eventLogs-checkbox-selectRow-${row.index}`"
+ @change="toggleSelectRow($refs.table, row.index)"
+ >
+ <span class="sr-only">{{ $t('global.table.selectItem') }}</span>
+ </b-form-checkbox>
+ </template>
+
+ <!-- Expand chevron icon -->
+ <template #cell(expandRow)="row">
+ <b-button
+ variant="link"
+ :aria-label="expandRowLabel"
+ :title="expandRowLabel"
+ class="btn-icon-only"
+ @click="toggleRowDetails(row)"
+ >
+ <icon-chevron />
+ </b-button>
+ </template>
+
+ <template #row-details="{ item }">
+ <b-container fluid>
+ <b-row>
+ <b-col>
+ <dl>
+ <!-- Name -->
+ <dt>{{ $t('pageEventLogs.table.name') }}:</dt>
+ <dd>{{ tableFormatter(item.name) }}</dd>
+ </dl>
+ <dl>
+ <!-- Type -->
+ <dt>{{ $t('pageEventLogs.table.type') }}:</dt>
+ <dd>{{ tableFormatter(item.type) }}</dd>
+ </dl>
+ </b-col>
+ <b-col>
+ <dl>
+ <!-- Modified date -->
+ <dt>{{ $t('pageEventLogs.table.modifiedDate') }}:</dt>
+ <dd v-if="item.modifiedDate">
+ {{ item.modifiedDate | formatDate }}
+ {{ item.modifiedDate | formatTime }}
+ </dd>
+ <dd v-else>--</dd>
+ </dl>
+ </b-col>
+ <b-col class="text-nowrap">
+ <b-button
+ class="btn btn-secondary float-right"
+ :href="item.additionalDataUri"
+ target="_blank"
+ >
+ <icon-download />{{ $t('pageEventLogs.additionalDataUri') }}
+ </b-button>
+ </b-col>
+ </b-row>
+ </b-container>
+ </template>
+
+ <!-- Severity column -->
+ <template #cell(severity)="{ value }">
+ <status-icon v-if="value" :status="statusIcon(value)" />
+ {{ value }}
+ </template>
+ <!-- Date column -->
+ <template #cell(date)="{ value }">
+ <p class="mb-0">{{ value | formatDate }}</p>
+ <p class="mb-0">{{ value | formatTime }}</p>
+ </template>
+
+ <!-- Status column -->
+ <template #cell(status)="row">
+ <b-form-checkbox
+ v-model="row.item.status"
+ name="switch"
+ switch
+ @change="changelogStatus(row.item)"
+ >
+ <span v-if="row.item.status">
+ {{ $t('pageEventLogs.resolved') }}
+ </span>
+ <span v-else> {{ $t('pageEventLogs.unresolved') }} </span>
+ </b-form-checkbox>
+ </template>
+ <template #cell(filterByStatus)="{ value }">
+ {{ value }}
+ </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"
+ :export-name="exportFileNameByDate('export')"
+ :data-test-id="`eventLogs-button-deleteRow-${row.index}`"
+ @click-table-action="onTableRowAction($event, row.item)"
+ >
+ <template #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>
+
+ <!-- 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-event-logs"
+ />
+ </b-col>
+ </b-row>
+ </b-container>
+</template>
+
+<script>
+import IconDelete from '@carbon/icons-vue/es/trash-can/20';
+import IconTrashcan from '@carbon/icons-vue/es/trash-can/20';
+import IconExport from '@carbon/icons-vue/es/document--export/20';
+import IconChevron from '@carbon/icons-vue/es/chevron--down/20';
+import IconDownload from '@carbon/icons-vue/es/download/20';
+import { omit } from 'lodash';
+
+import PageTitle from '@/components/Global/PageTitle';
+import StatusIcon from '@/components/Global/StatusIcon';
+import Search from '@/components/Global/Search';
+import TableCellCount from '@/components/Global/TableCellCount';
+import TableDateFilter from '@/components/Global/TableDateFilter';
+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, {
+ 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: {
+ IconDelete,
+ IconExport,
+ IconTrashcan,
+ IconChevron,
+ IconDownload,
+ PageTitle,
+ Search,
+ StatusIcon,
+ TableCellCount,
+ TableFilter,
+ 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: 'expandRow',
+ label: '',
+ tdClass: 'table-row-expand',
+ },
+ {
+ key: 'checkbox',
+ sortable: false,
+ },
+ {
+ key: 'id',
+ label: this.$t('pageEventLogs.table.id'),
+ sortable: true,
+ },
+ {
+ key: 'severity',
+ label: this.$t('pageEventLogs.table.severity'),
+ sortable: true,
+ tdClass: 'text-nowrap',
+ },
+ {
+ key: 'date',
+ label: this.$t('pageEventLogs.table.date'),
+ sortable: true,
+ tdClass: 'text-nowrap',
+ },
+ {
+ key: 'description',
+ label: this.$t('pageEventLogs.table.description'),
+ tdClass: 'text-break',
+ },
+ {
+ key: 'status',
+ label: this.$t('pageEventLogs.table.status'),
+ },
+ {
+ key: 'actions',
+ sortable: false,
+ label: '',
+ tdClass: 'text-right text-nowrap',
+ },
+ ],
+ tableFilters: [
+ {
+ key: 'severity',
+ label: this.$t('pageEventLogs.table.severity'),
+ values: ['OK', 'Warning', 'Critical'],
+ },
+ {
+ key: 'filterByStatus',
+ label: this.$t('pageEventLogs.table.status'),
+ values: ['Resolved', 'Unresolved'],
+ },
+ ],
+ expandRowLabel,
+ activeFilters: [],
+ batchActions: [
+ {
+ value: 'delete',
+ label: this.$t('global.action.delete'),
+ },
+ ],
+ 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.exportAllLogs()}`;
+ },
+ filteredRows() {
+ return this.searchFilter
+ ? this.searchTotalFilteredRows
+ : this.filteredLogs.length;
+ },
+ allLogs() {
+ 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'),
+ },
+ ],
+ };
+ });
+ },
+ 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('eventLog/getEventLogData')
+ .finally(() => this.endLoader());
+ },
+ methods: {
+ changelogStatus(row) {
+ this.$store
+ .dispatch('eventLog/updateEventLogStatus', {
+ uri: row.uri,
+ status: row.status,
+ })
+ .then((success) => {
+ this.successToast(success);
+ })
+ .catch(({ message }) => this.errorToast(message));
+ },
+ deleteAllLogs() {
+ this.$bvModal
+ .msgBoxConfirm(this.$t('pageEventLogs.modal.deleteAllMessage'), {
+ title: this.$t('pageEventLogs.modal.deleteAllTitle'),
+ okTitle: this.$t('global.action.delete'),
+ okVariant: 'danger',
+ cancelTitle: this.$t('global.action.cancel'),
+ })
+ .then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ this.$store
+ .dispatch('eventLog/deleteAllEventLogs', this.allLogs)
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ }
+ });
+ },
+ 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);
+ }
+ });
+ });
+ },
+ exportAllLogs() {
+ {
+ return this.$store.getters['eventLog/allEvents'].map((eventLogs) => {
+ const allEventLogsString = JSON.stringify(eventLogs);
+ return allEventLogsString;
+ });
+ }
+ },
+ onFilterChange({ activeFilters }) {
+ this.activeFilters = activeFilters;
+ },
+ onSortCompare(a, b, key) {
+ if (key === 'severity') {
+ return this.sortStatus(a, b, key);
+ }
+ },
+ 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'),
+ cancelTitle: this.$t('global.action.cancel'),
+ })
+ .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'),
+ cancelTitle: this.$t('global.action.cancel'),
+ }
+ )
+ .then((deleteConfirmed) => {
+ if (deleteConfirmed) {
+ if (this.selectedRows.length === this.allLogs.length) {
+ this.$store
+ .dispatch(
+ 'eventLog/deleteAllEventLogs',
+ this.selectedRows.length
+ )
+ .then((message) => this.successToast(message))
+ .catch(({ message }) => this.errorToast(message));
+ } else {
+ this.deleteLogs(uris);
+ }
+ }
+ });
+ }
+ },
+ onChangeDateTimeFilter({ fromDate, toDate }) {
+ this.filterStartDate = fromDate;
+ this.filterEndDate = toDate;
+ },
+ onFiltered(filteredItems) {
+ this.searchTotalFilteredRows = filteredItems.length;
+ },
+ // Create export file name based on date
+ exportFileNameByDate(value) {
+ let date = new Date();
+ date =
+ date.toISOString().slice(0, 10) +
+ '_' +
+ date.toString().split(':').join('-').split(' ')[4];
+ let fileName;
+ if (value === 'export') {
+ fileName = 'event_log_';
+ } else {
+ fileName = 'all_event_logs_';
+ }
+ return fileName + date;
+ },
+ resolveLogs() {
+ this.$store
+ .dispatch('eventLog/resolveEventLogs', this.selectedRows)
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') {
+ this.successToast(message);
+ } else if (type === 'error') {
+ this.errorToast(message);
+ }
+ });
+ });
+ },
+ unresolveLogs() {
+ this.$store
+ .dispatch('eventLog/unresolveEventLogs', this.selectedRows)
+ .then((messages) => {
+ messages.forEach(({ type, message }) => {
+ if (type === 'success') {
+ this.successToast(message);
+ } else if (type === 'error') {
+ this.errorToast(message);
+ }
+ });
+ });
+ },
+ },
+};
+</script>
diff --git a/src/views/Logs/EventLogs/index.js b/src/views/Logs/EventLogs/index.js
new file mode 100644
index 00000000..521efde4
--- /dev/null
+++ b/src/views/Logs/EventLogs/index.js
@@ -0,0 +1,2 @@
+import EventLogs from './EventLogs.vue';
+export default EventLogs;