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('pageEventLogs.table.searchLogs')" 8 data-test-id="eventLogs-input-searchLogs" 9 @change-search="onChangeSearchInput" 10 @clear-search="onClearSearchInput" 11 /> 12 <div class="ml-sm-4"> 13 <table-cell-count 14 :filtered-items-count="filteredRows" 15 :total-number-of-cells="allLogs.length" 16 ></table-cell-count> 17 </div> 18 </b-col> 19 <b-col sm="8" md="7" xl="6"> 20 <table-date-filter @change="onChangeDateTimeFilter" /> 21 </b-col> 22 </b-row> 23 <b-row> 24 <b-col class="text-right"> 25 <table-filter :filters="tableFilters" @filter-change="onFilterChange" /> 26 <b-button 27 variant="link" 28 :disabled="allLogs.length === 0" 29 @click="deleteAllLogs" 30 > 31 <icon-delete /> {{ $t('global.action.deleteAll') }} 32 </b-button> 33 <b-button 34 variant="primary" 35 :class="{ disabled: allLogs.length === 0 }" 36 :download="exportFileNameByDate()" 37 :href="href" 38 > 39 <icon-export /> {{ $t('global.action.exportAll') }} 40 </b-button> 41 </b-col> 42 </b-row> 43 <b-row> 44 <b-col> 45 <table-toolbar 46 ref="toolbar" 47 :selected-items-count="selectedRows.length" 48 :actions="batchActions" 49 @clear-selected="clearSelectedRows($refs.table)" 50 @batch-action="onBatchAction" 51 > 52 <template #toolbar-buttons> 53 <b-button v-if="!hideToggle" variant="primary" @click="resolveLogs"> 54 {{ $t('pageEventLogs.resolve') }} 55 </b-button> 56 <b-button 57 v-if="!hideToggle" 58 variant="primary" 59 @click="unresolveLogs" 60 > 61 {{ $t('pageEventLogs.unresolve') }} 62 </b-button> 63 <table-toolbar-export 64 :data="batchExportData" 65 :file-name="exportFileNameByDate()" 66 /> 67 </template> 68 </table-toolbar> 69 <b-table 70 id="table-event-logs" 71 ref="table" 72 responsive="md" 73 selectable 74 no-select-on-click 75 sort-icon-left 76 hover 77 no-sort-reset 78 sort-desc 79 show-empty 80 sort-by="id" 81 :fields="fields" 82 :items="filteredLogs" 83 :sort-compare="onSortCompare" 84 :empty-text="$t('global.table.emptyMessage')" 85 :empty-filtered-text="$t('global.table.emptySearchMessage')" 86 :per-page="perPage" 87 :current-page="currentPage" 88 :filter="searchFilter" 89 :busy="isBusy" 90 @filtered="onFiltered" 91 @row-selected="onRowSelected($event, filteredLogs.length)" 92 > 93 <!-- Checkbox column --> 94 <template #head(checkbox)> 95 <b-form-checkbox 96 v-model="tableHeaderCheckboxModel" 97 data-test-id="eventLogs-checkbox-selectAll" 98 :indeterminate="tableHeaderCheckboxIndeterminate" 99 @change="onChangeHeaderCheckbox($refs.table)" 100 > 101 <span class="sr-only">{{ $t('global.table.selectAll') }}</span> 102 </b-form-checkbox> 103 </template> 104 <template #cell(checkbox)="row"> 105 <b-form-checkbox 106 v-model="row.rowSelected" 107 :data-test-id="`eventLogs-checkbox-selectRow-${row.index}`" 108 @change="toggleSelectRow($refs.table, row.index)" 109 > 110 <span class="sr-only">{{ $t('global.table.selectItem') }}</span> 111 </b-form-checkbox> 112 </template> 113 114 <!-- Expand chevron icon --> 115 <template #cell(expandRow)="row"> 116 <b-button 117 variant="link" 118 :aria-label="expandRowLabel" 119 :title="expandRowLabel" 120 class="btn-icon-only" 121 @click="toggleRowDetails(row)" 122 > 123 <icon-chevron /> 124 </b-button> 125 </template> 126 127 <template #row-details="{ item }"> 128 <b-container fluid> 129 <b-row> 130 <b-col> 131 <dl> 132 <!-- Name --> 133 <dt>{{ $t('pageEventLogs.table.name') }}:</dt> 134 <dd>{{ dataFormatter(item.name) }}</dd> 135 </dl> 136 <dl> 137 <!-- Type --> 138 <dt>{{ $t('pageEventLogs.table.type') }}:</dt> 139 <dd>{{ dataFormatter(item.type) }}</dd> 140 </dl> 141 </b-col> 142 <b-col> 143 <dl> 144 <!-- Modified date --> 145 <dt>{{ $t('pageEventLogs.table.modifiedDate') }}:</dt> 146 <dd v-if="item.modifiedDate"> 147 {{ item.modifiedDate | formatDate }} 148 {{ item.modifiedDate | formatTime }} 149 </dd> 150 <dd v-else>--</dd> 151 </dl> 152 </b-col> 153 <b-col class="text-nowrap"> 154 <b-button 155 class="btn btn-secondary float-right" 156 :href="item.additionalDataUri" 157 target="_blank" 158 > 159 <icon-download />{{ $t('pageEventLogs.additionalDataUri') }} 160 </b-button> 161 </b-col> 162 </b-row> 163 </b-container> 164 </template> 165 166 <!-- Severity column --> 167 <template #cell(severity)="{ value }"> 168 <status-icon v-if="value" :status="statusIcon(value)" /> 169 {{ value }} 170 </template> 171 <!-- Date column --> 172 <template #cell(date)="{ value }"> 173 <p class="mb-0">{{ value | formatDate }}</p> 174 <p class="mb-0">{{ value | formatTime }}</p> 175 </template> 176 177 <!-- Status column --> 178 <template #cell(status)="row"> 179 <b-form-checkbox 180 v-model="row.item.status" 181 name="switch" 182 switch 183 @change="changelogStatus(row.item)" 184 > 185 <span v-if="row.item.status"> 186 {{ $t('pageEventLogs.resolved') }} 187 </span> 188 <span v-else> {{ $t('pageEventLogs.unresolved') }} </span> 189 </b-form-checkbox> 190 </template> 191 <template #cell(filterByStatus)="{ value }"> 192 {{ value }} 193 </template> 194 195 <!-- Actions column --> 196 <template #cell(actions)="row"> 197 <table-row-action 198 v-for="(action, index) in row.item.actions" 199 :key="index" 200 :value="action.value" 201 :title="action.title" 202 :row-data="row.item" 203 :export-name="exportFileNameByDate('export')" 204 :data-test-id="`eventLogs-button-deleteRow-${row.index}`" 205 @click-table-action="onTableRowAction($event, row.item)" 206 > 207 <template #icon> 208 <icon-export v-if="action.value === 'export'" /> 209 <icon-trashcan v-if="action.value === 'delete'" /> 210 </template> 211 </table-row-action> 212 </template> 213 </b-table> 214 </b-col> 215 </b-row> 216 217 <!-- Table pagination --> 218 <b-row> 219 <b-col sm="6"> 220 <b-form-group 221 class="table-pagination-select" 222 :label="$t('global.table.itemsPerPage')" 223 label-for="pagination-items-per-page" 224 > 225 <b-form-select 226 id="pagination-items-per-page" 227 v-model="perPage" 228 :options="itemsPerPageOptions" 229 /> 230 </b-form-group> 231 </b-col> 232 <b-col sm="6"> 233 <b-pagination 234 v-model="currentPage" 235 first-number 236 last-number 237 :per-page="perPage" 238 :total-rows="getTotalRowCount(filteredRows)" 239 aria-controls="table-event-logs" 240 /> 241 </b-col> 242 </b-row> 243 </b-container> 244</template> 245 246<script> 247import IconDelete from '@carbon/icons-vue/es/trash-can/20'; 248import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; 249import IconExport from '@carbon/icons-vue/es/document--export/20'; 250import IconChevron from '@carbon/icons-vue/es/chevron--down/20'; 251import IconDownload from '@carbon/icons-vue/es/download/20'; 252import { omit } from 'lodash'; 253 254import PageTitle from '@/components/Global/PageTitle'; 255import StatusIcon from '@/components/Global/StatusIcon'; 256import Search from '@/components/Global/Search'; 257import TableCellCount from '@/components/Global/TableCellCount'; 258import TableDateFilter from '@/components/Global/TableDateFilter'; 259import TableFilter from '@/components/Global/TableFilter'; 260import TableRowAction from '@/components/Global/TableRowAction'; 261import TableToolbar from '@/components/Global/TableToolbar'; 262import TableToolbarExport from '@/components/Global/TableToolbarExport'; 263 264import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; 265import TableFilterMixin from '@/components/Mixins/TableFilterMixin'; 266import BVPaginationMixin, { 267 currentPage, 268 perPage, 269 itemsPerPageOptions, 270} from '@/components/Mixins/BVPaginationMixin'; 271import BVTableSelectableMixin, { 272 selectedRows, 273 tableHeaderCheckboxModel, 274 tableHeaderCheckboxIndeterminate, 275} from '@/components/Mixins/BVTableSelectableMixin'; 276import BVToastMixin from '@/components/Mixins/BVToastMixin'; 277import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin'; 278import TableSortMixin from '@/components/Mixins/TableSortMixin'; 279import TableRowExpandMixin, { 280 expandRowLabel, 281} from '@/components/Mixins/TableRowExpandMixin'; 282import SearchFilterMixin, { 283 searchFilter, 284} from '@/components/Mixins/SearchFilterMixin'; 285 286export default { 287 components: { 288 IconDelete, 289 IconExport, 290 IconTrashcan, 291 IconChevron, 292 IconDownload, 293 PageTitle, 294 Search, 295 StatusIcon, 296 TableCellCount, 297 TableFilter, 298 TableRowAction, 299 TableToolbar, 300 TableToolbarExport, 301 TableDateFilter, 302 }, 303 mixins: [ 304 BVPaginationMixin, 305 BVTableSelectableMixin, 306 BVToastMixin, 307 LoadingBarMixin, 308 TableFilterMixin, 309 DataFormatterMixin, 310 TableSortMixin, 311 TableRowExpandMixin, 312 SearchFilterMixin, 313 ], 314 beforeRouteLeave(to, from, next) { 315 // Hide loader if the user navigates to another page 316 // before request is fulfilled. 317 this.hideLoader(); 318 next(); 319 }, 320 data() { 321 return { 322 isBusy: true, 323 fields: [ 324 { 325 key: 'expandRow', 326 label: '', 327 tdClass: 'table-row-expand', 328 }, 329 { 330 key: 'checkbox', 331 sortable: false, 332 }, 333 { 334 key: 'id', 335 label: this.$t('pageEventLogs.table.id'), 336 sortable: true, 337 }, 338 { 339 key: 'severity', 340 label: this.$t('pageEventLogs.table.severity'), 341 sortable: true, 342 tdClass: 'text-nowrap', 343 }, 344 { 345 key: 'date', 346 label: this.$t('pageEventLogs.table.date'), 347 sortable: true, 348 tdClass: 'text-nowrap', 349 }, 350 { 351 key: 'description', 352 label: this.$t('pageEventLogs.table.description'), 353 tdClass: 'text-break', 354 }, 355 process.env.VUE_APP_EVENT_LOGS_TOGGLE_BUTTON_DISABLED === 'true' 356 ? {} 357 : { 358 key: 'status', 359 label: this.$t('pageEventLogs.table.status'), 360 }, 361 { 362 key: 'actions', 363 sortable: false, 364 label: '', 365 tdClass: 'text-right text-nowrap', 366 }, 367 ], 368 tableFilters: 369 process.env.VUE_APP_EVENT_LOGS_TOGGLE_BUTTON_DISABLED === 'true' 370 ? [ 371 { 372 key: 'severity', 373 label: this.$t('pageEventLogs.table.severity'), 374 values: ['OK', 'Warning', 'Critical'], 375 }, 376 ] 377 : [ 378 { 379 key: 'severity', 380 label: this.$t('pageEventLogs.table.severity'), 381 values: ['OK', 'Warning', 'Critical'], 382 }, 383 { 384 key: 'filterByStatus', 385 label: this.$t('pageEventLogs.table.status'), 386 values: ['Resolved', 'Unresolved'], 387 }, 388 ], 389 expandRowLabel, 390 activeFilters: [], 391 batchActions: 392 process.env.VUE_APP_EVENT_LOGS_DELETE_BUTTON_DISABLED === 'true' 393 ? [] 394 : [ 395 { 396 value: 'delete', 397 label: this.$t('global.action.delete'), 398 }, 399 ], 400 currentPage: currentPage, 401 filterStartDate: null, 402 filterEndDate: null, 403 itemsPerPageOptions: itemsPerPageOptions, 404 perPage: perPage, 405 searchFilter: searchFilter, 406 searchTotalFilteredRows: 0, 407 selectedRows: selectedRows, 408 tableHeaderCheckboxModel: tableHeaderCheckboxModel, 409 tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate, 410 hideToggle: 411 process.env.VUE_APP_EVENT_LOGS_TOGGLE_BUTTON_DISABLED === 'true', 412 hideDelete: 413 process.env.VUE_APP_EVENT_LOGS_DELETE_BUTTON_DISABLED === 'true', 414 }; 415 }, 416 computed: { 417 href() { 418 return `data:text/json;charset=utf-8,${this.exportAllLogs()}`; 419 }, 420 filteredRows() { 421 return this.searchFilter 422 ? this.searchTotalFilteredRows 423 : this.filteredLogs.length; 424 }, 425 allLogs() { 426 return this.$store.getters['eventLog/allEvents'].map((event) => { 427 return { 428 ...event, 429 actions: this.hideDelete 430 ? [ 431 { 432 value: 'export', 433 title: this.$t('global.action.export'), 434 }, 435 ] 436 : [ 437 { 438 value: 'export', 439 title: this.$t('global.action.export'), 440 }, 441 { 442 value: 'delete', 443 title: this.$t('global.action.delete'), 444 }, 445 ], 446 }; 447 }); 448 }, 449 batchExportData() { 450 return this.selectedRows.map((row) => omit(row, 'actions')); 451 }, 452 filteredLogsByDate() { 453 return this.getFilteredTableDataByDate( 454 this.allLogs, 455 this.filterStartDate, 456 this.filterEndDate, 457 ); 458 }, 459 filteredLogs() { 460 return this.getFilteredTableData( 461 this.filteredLogsByDate, 462 this.activeFilters, 463 ); 464 }, 465 }, 466 created() { 467 this.startLoader(); 468 this.$store.dispatch('eventLog/getEventLogData').finally(() => { 469 this.endLoader(); 470 this.isBusy = false; 471 }); 472 }, 473 methods: { 474 changelogStatus(row) { 475 this.$store 476 .dispatch('eventLog/updateEventLogStatus', { 477 uri: row.uri, 478 status: row.status, 479 }) 480 .then((success) => { 481 this.successToast(success); 482 }) 483 .catch(({ message }) => this.errorToast(message)); 484 }, 485 deleteAllLogs() { 486 this.$bvModal 487 .msgBoxConfirm(this.$t('pageEventLogs.modal.deleteAllMessage'), { 488 title: this.$t('pageEventLogs.modal.deleteAllTitle'), 489 okTitle: this.$t('global.action.delete'), 490 okVariant: 'danger', 491 cancelTitle: this.$t('global.action.cancel'), 492 autoFocusButton: 'cancel', 493 }) 494 .then((deleteConfirmed) => { 495 if (deleteConfirmed) { 496 this.$store 497 .dispatch('eventLog/deleteAllEventLogs', this.allLogs) 498 .then((message) => this.successToast(message)) 499 .catch(({ message }) => this.errorToast(message)); 500 } 501 }); 502 }, 503 deleteLogs(uris) { 504 this.$store 505 .dispatch('eventLog/deleteEventLogs', uris) 506 .then((messages) => { 507 messages.forEach(({ type, message }) => { 508 if (type === 'success') { 509 this.successToast(message); 510 } else if (type === 'error') { 511 this.errorToast(message); 512 } 513 }); 514 }); 515 }, 516 exportAllLogs() { 517 { 518 return this.$store.getters['eventLog/allEvents'].map((eventLogs) => { 519 const allEventLogsString = JSON.stringify(eventLogs); 520 return allEventLogsString; 521 }); 522 } 523 }, 524 onFilterChange({ activeFilters }) { 525 this.activeFilters = activeFilters; 526 }, 527 onSortCompare(a, b, key) { 528 if (key === 'severity') { 529 return this.sortStatus(a, b, key); 530 } 531 }, 532 onTableRowAction(action, { uri }) { 533 if (action === 'delete') { 534 this.$bvModal 535 .msgBoxConfirm(this.$tc('pageEventLogs.modal.deleteMessage'), { 536 title: this.$tc('pageEventLogs.modal.deleteTitle'), 537 okTitle: this.$t('global.action.delete'), 538 cancelTitle: this.$t('global.action.cancel'), 539 autoFocusButton: 'ok', 540 }) 541 .then((deleteConfirmed) => { 542 if (deleteConfirmed) this.deleteLogs([uri]); 543 }); 544 } 545 }, 546 onBatchAction(action) { 547 if (action === 'delete') { 548 const uris = this.selectedRows.map((row) => row.uri); 549 this.$bvModal 550 .msgBoxConfirm( 551 this.$tc( 552 'pageEventLogs.modal.deleteMessage', 553 this.selectedRows.length, 554 ), 555 { 556 title: this.$tc( 557 'pageEventLogs.modal.deleteTitle', 558 this.selectedRows.length, 559 ), 560 okTitle: this.$t('global.action.delete'), 561 cancelTitle: this.$t('global.action.cancel'), 562 autoFocusButton: 'ok', 563 }, 564 ) 565 .then((deleteConfirmed) => { 566 if (deleteConfirmed) { 567 if (this.selectedRows.length === this.allLogs.length) { 568 this.$store 569 .dispatch( 570 'eventLog/deleteAllEventLogs', 571 this.selectedRows.length, 572 ) 573 .then(() => { 574 this.successToast( 575 this.$tc( 576 'pageEventLogs.toast.successDelete', 577 uris.length, 578 ), 579 ); 580 }) 581 .catch(({ message }) => this.errorToast(message)); 582 } else { 583 this.deleteLogs(uris); 584 } 585 } 586 }); 587 } 588 }, 589 onChangeDateTimeFilter({ fromDate, toDate }) { 590 this.filterStartDate = fromDate; 591 this.filterEndDate = toDate; 592 }, 593 onFiltered(filteredItems) { 594 this.searchTotalFilteredRows = filteredItems.length; 595 }, 596 // Create export file name based on date 597 exportFileNameByDate(value) { 598 let date = new Date(); 599 date = 600 date.toISOString().slice(0, 10) + 601 '_' + 602 date.toString().split(':').join('-').split(' ')[4]; 603 let fileName; 604 if (value === 'export') { 605 fileName = 'event_log_'; 606 } else { 607 fileName = 'all_event_logs_'; 608 } 609 return fileName + date; 610 }, 611 resolveLogs() { 612 this.$store 613 .dispatch('eventLog/resolveEventLogs', this.selectedRows) 614 .then((messages) => { 615 messages.forEach(({ type, message }) => { 616 if (type === 'success') { 617 this.successToast(message); 618 } else if (type === 'error') { 619 this.errorToast(message); 620 } 621 }); 622 }); 623 }, 624 unresolveLogs() { 625 this.$store 626 .dispatch('eventLog/unresolveEventLogs', this.selectedRows) 627 .then((messages) => { 628 messages.forEach(({ type, message }) => { 629 if (type === 'success') { 630 this.successToast(message); 631 } else if (type === 'error') { 632 this.errorToast(message); 633 } 634 }); 635 }); 636 }, 637 }, 638}; 639</script> 640