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