xref: /openbmc/webui-vue/src/views/Logs/PostCodeLogs/PostCodeLogs.vue (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
1<template>
2  <b-container fluid="xl">
3    <page-title />
4    <b-row class="align-items-start">
5      <b-col sm="8" xl="6" class="d-sm-flex align-items-end mb-4">
6        <search
7          :placeholder="$t('pagePostCodeLogs.table.searchLogs')"
8          @change-search="onChangeSearchInput"
9          @clear-search="onClearSearchInput"
10        />
11        <div class="ms-sm-4">
12          <table-cell-count
13            :filtered-items-count="filteredRows"
14            :total-number-of-cells="allLogs.length"
15          ></table-cell-count>
16        </div>
17      </b-col>
18      <b-col sm="8" md="7" xl="6">
19        <table-date-filter @change="onChangeDateTimeFilter" />
20      </b-col>
21    </b-row>
22    <b-row>
23      <b-col xl="12" class="text-end">
24        <b-button
25          variant="link"
26          :disabled="allLogs.length === 0"
27          @click="deleteAllLogs"
28        >
29          <icon-delete /> {{ $t('global.action.deleteAll') }}
30        </b-button>
31        <b-button
32          variant="primary"
33          :disabled="allLogs.length === 0"
34          :download="exportFileNameByDate()"
35          :href="href"
36        >
37          <icon-export />
38          {{ $t('pagePostCodeLogs.button.exportAll') }}
39        </b-button>
40      </b-col>
41    </b-row>
42    <b-row>
43      <b-col>
44        <table-toolbar
45          ref="toolbar"
46          :selected-items-count="
47            Array.isArray(selectedRows) ? selectedRows.length : 0
48          "
49          @clear-selected="clearSelectedRows($refs.table)"
50        >
51          <template #toolbar-buttons>
52            <table-toolbar-export
53              :data="batchExportData"
54              :file-name="exportFileNameByDate()"
55            />
56          </template>
57        </table-toolbar>
58        <b-table
59          id="table-post-code-logs"
60          ref="table"
61          responsive="md"
62          selectable
63          no-select-on-click
64          sort-icon-left
65          hover
66          must-sort
67          thead-class="table-light"
68          :sort-desc="[true]"
69          show-empty
70          :sort-by="['id']"
71          :fields="fields"
72          :items="filteredLogs"
73          :empty-text="$t('global.table.emptyMessage')"
74          :empty-filtered-text="$t('global.table.emptySearchMessage')"
75          :per-page="perPage"
76          :current-page="currentPage"
77          :filter="searchFilter"
78          :busy="isBusy"
79          @filtered="onFiltered"
80          @row-selected="onRowSelected($event, filteredLogs.length)"
81        >
82          <!-- Checkbox column -->
83          <template #head(checkbox)>
84            <b-form-checkbox
85              v-model="tableHeaderCheckboxModel"
86              data-test-id="postCode-checkbox-selectAll"
87              :indeterminate="tableHeaderCheckboxIndeterminate"
88              @change="onChangeHeaderCheckbox($refs.table, $event)"
89            >
90              <span class="visually-hidden-focusable">
91                {{ $t('global.table.selectAll') }}
92              </span>
93            </b-form-checkbox>
94          </template>
95          <template #cell(checkbox)="row">
96            <b-form-checkbox
97              v-model="row.rowSelected"
98              :data-test-id="`postCode-checkbox-selectRow-${row.index}`"
99              @change="toggleSelectRow($refs.table, row.index)"
100            >
101              <span class="visually-hidden-focusable">
102                {{ $t('global.table.selectItem') }}
103              </span>
104            </b-form-checkbox>
105          </template>
106          <!-- Date column -->
107          <template #cell(date)="{ value }">
108            <p class="mb-0">{{ $filters.formatDate(value) }}</p>
109            <p class="mb-0">{{ $filters.formatTime(value) }}</p>
110          </template>
111
112          <!-- Actions column -->
113          <template #cell(actions)="row">
114            <table-row-action
115              v-for="(action, index) in row.item.actions"
116              :key="index"
117              :value="action.value"
118              :title="action.title"
119              :row-data="row.item"
120              :btn-icon-only="true"
121              :export-name="exportFileNameByDate(action.value)"
122              :download-location="
123                action.value === 'download' ? '' : row.item.uri
124              "
125              :download-in-new-tab="false"
126              :show-button="action.value === 'download' ? true : false"
127              @click-table-action="onRowAction(action.value, row.item)"
128            >
129              <template #icon>
130                <icon-export v-if="action.value === 'export'" />
131                <icon-download v-if="action.value === 'download'" />
132              </template>
133            </table-row-action>
134          </template>
135        </b-table>
136      </b-col>
137    </b-row>
138
139    <!-- Table pagination -->
140    <b-row>
141      <b-col sm="6">
142        <b-form-group
143          class="table-pagination-select"
144          :label="$t('global.table.itemsPerPage')"
145          label-for="pagination-items-per-page"
146        >
147          <b-form-select
148            id="pagination-items-per-page"
149            v-model="perPage"
150            :options="itemsPerPageOptions"
151          />
152        </b-form-group>
153      </b-col>
154      <b-col sm="6">
155        <b-pagination
156          v-model="currentPage"
157          first-number
158          last-number
159          :per-page="perPage"
160          :total-rows="getTotalRowCount(filteredRows)"
161          aria-controls="table-post-code-logs"
162        />
163      </b-col>
164    </b-row>
165  </b-container>
166</template>
167
168<script>
169import IconDelete from '@carbon/icons-vue/es/trash-can/20';
170import IconDownload from '@carbon/icons-vue/es/download/20';
171import IconExport from '@carbon/icons-vue/es/document--export/20';
172import { omit } from 'lodash';
173import PageTitle from '@/components/Global/PageTitle';
174import Search from '@/components/Global/Search';
175import TableCellCount from '@/components/Global/TableCellCount';
176import TableDateFilter from '@/components/Global/TableDateFilter';
177import TableRowAction from '@/components/Global/TableRowAction';
178import TableToolbar from '@/components/Global/TableToolbar';
179import TableToolbarExport from '@/components/Global/TableToolbarExport';
180import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin';
181import TableFilterMixin from '@/components/Mixins/TableFilterMixin';
182import BVPaginationMixin, {
183  currentPage,
184  perPage,
185  itemsPerPageOptions,
186} from '@/components/Mixins/BVPaginationMixin';
187import BVTableSelectableMixin, {
188  selectedRows,
189  tableHeaderCheckboxModel,
190  tableHeaderCheckboxIndeterminate,
191} from '@/components/Mixins/BVTableSelectableMixin';
192import BVToastMixin from '@/components/Mixins/BVToastMixin';
193import TableSortMixin from '@/components/Mixins/TableSortMixin';
194import TableRowExpandMixin, {
195  expandRowLabel,
196} from '@/components/Mixins/TableRowExpandMixin';
197import SearchFilterMixin, {
198  searchFilter,
199} from '@/components/Mixins/SearchFilterMixin';
200import { useI18n } from 'vue-i18n';
201import i18n from '@/i18n';
202import { useModal } from 'bootstrap-vue-next';
203
204export default {
205  components: {
206    IconDelete,
207    IconExport,
208    IconDownload,
209    PageTitle,
210    Search,
211    TableCellCount,
212    TableRowAction,
213    TableToolbar,
214    TableToolbarExport,
215    TableDateFilter,
216  },
217  mixins: [
218    BVPaginationMixin,
219    BVTableSelectableMixin,
220    BVToastMixin,
221    LoadingBarMixin,
222    TableFilterMixin,
223    TableSortMixin,
224    TableRowExpandMixin,
225    SearchFilterMixin,
226  ],
227  beforeRouteLeave(to, from, next) {
228    // Hide loader if the user navigates to another page
229    // before request is fulfilled.
230    this.hideLoader();
231    next();
232  },
233  setup() {
234    const bvModal = useModal();
235    return { bvModal };
236  },
237  data() {
238    return {
239      $t: useI18n().t,
240      isBusy: true,
241      fields: [
242        {
243          key: 'checkbox',
244          sortable: false,
245        },
246        {
247          key: 'date',
248          label: i18n.global.t('pagePostCodeLogs.table.created'),
249          sortable: true,
250        },
251        {
252          key: 'timeStampOffset',
253          label: i18n.global.t('pagePostCodeLogs.table.timeStampOffset'),
254        },
255        {
256          key: 'bootCount',
257          label: i18n.global.t('pagePostCodeLogs.table.bootCount'),
258        },
259        {
260          key: 'postCode',
261          label: i18n.global.t('pagePostCodeLogs.table.postCode'),
262        },
263        {
264          key: 'actions',
265          label: '',
266          tdClass: 'text-end text-nowrap',
267        },
268      ],
269      expandRowLabel,
270      activeFilters: [],
271      currentPage: currentPage,
272      filterStartDate: null,
273      filterEndDate: null,
274      itemsPerPageOptions: itemsPerPageOptions,
275      perPage: perPage,
276      searchFilter: searchFilter,
277      searchTotalFilteredRows: 0,
278      selectedRows: selectedRows,
279      tableHeaderCheckboxModel: tableHeaderCheckboxModel,
280      tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate,
281    };
282  },
283  computed: {
284    href() {
285      return `data:text/json;charset=utf-8,${this.exportAllLogsString()}`;
286    },
287    filteredRows() {
288      return this.searchFilter
289        ? this.searchTotalFilteredRows
290        : this.filteredLogs.length;
291    },
292    allLogs() {
293      return this.$store.getters['postCodeLogs/allPostCodes'].map(
294        (postCodes) => {
295          return {
296            ...postCodes,
297            actions: [
298              {
299                value: 'export',
300                title: i18n.global.t('pagePostCodeLogs.action.exportLogs'),
301              },
302              {
303                value: 'download',
304                title: i18n.global.t('pagePostCodeLogs.action.downloadDetails'),
305              },
306            ],
307          };
308        },
309      );
310    },
311    batchExportData() {
312      return this.selectedRows.map((row) => omit(row, 'actions'));
313    },
314    filteredLogsByDate() {
315      return this.getFilteredTableDataByDate(
316        this.allLogs,
317        this.filterStartDate,
318        this.filterEndDate,
319      );
320    },
321    filteredLogs() {
322      return this.getFilteredTableData(
323        this.filteredLogsByDate,
324        this.activeFilters,
325      );
326    },
327  },
328  created() {
329    this.startLoader();
330    this.$store.dispatch('postCodeLogs/getPostCodesLogData').finally(() => {
331      this.endLoader();
332      this.isBusy = false;
333    });
334  },
335  methods: {
336    onRowAction(action, item) {
337      if (action === 'download') {
338        this.$store
339          .dispatch('postCodeLogs/downloadEntry', item.uri)
340          .then((blob) => {
341            const link = document.createElement('a');
342            link.href = URL.createObjectURL(blob);
343            link.download = this.exportFileNameByDate('download');
344            link.click();
345            URL.revokeObjectURL(link.href);
346          })
347          .catch(({ message }) => this.errorToast(message));
348      }
349    },
350    async deleteAllLogs() {
351      const deleteConfirmed = await this.confirmDialog(
352        i18n.global.t('pageEventLogs.modal.deleteAllMessage'),
353        {
354          title: i18n.global.t('pageEventLogs.modal.deleteAllTitle'),
355          okTitle: i18n.global.t('global.action.delete'),
356          okVariant: 'danger',
357          cancelTitle: i18n.global.t('global.action.cancel'),
358          autoFocusButton: 'cancel',
359        },
360      );
361      if (deleteConfirmed) {
362        this.$store
363          .dispatch('postCodeLogs/deleteAllPostCodeLogs', this.allLogs)
364          .then((message) => this.successToast(message))
365          .catch(({ message }) => this.errorToast(message));
366      }
367    },
368    exportAllLogsString() {
369      {
370        return this.$store.getters['postCodeLogs/allPostCodes'].map(
371          (postCodes) => {
372            const allLogsString = JSON.stringify(postCodes);
373            return allLogsString;
374          },
375        );
376      }
377    },
378    onFilterChange({ activeFilters }) {
379      this.activeFilters = activeFilters;
380    },
381    onChangeDateTimeFilter({ fromDate, toDate }) {
382      this.filterStartDate = fromDate;
383      this.filterEndDate = toDate;
384    },
385    onFiltered(filteredItems) {
386      this.searchTotalFilteredRows = filteredItems.length;
387    },
388    // Create export file name based on date and action
389    exportFileNameByDate(value) {
390      let date = new Date();
391      date =
392        date.toISOString().slice(0, 10) +
393        '_' +
394        date.toString().split(':').join('-').split(' ')[4];
395      let fileName;
396      if (value === 'download') {
397        fileName = i18n.global.t('pagePostCodeLogs.downloadFilePrefix');
398      } else if (value === 'export') {
399        fileName = i18n.global.t('pagePostCodeLogs.exportFilePrefix');
400      } else {
401        fileName = i18n.global.t('pagePostCodeLogs.allExportFilePrefix');
402      }
403      return fileName + date;
404    },
405    confirmDialog(message, options = {}) {
406      return this.$confirm({ message, ...options });
407    },
408  },
409};
410</script>
411