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 }} 148 {{ item.modifiedDate }} 149 </dd> 150 <dd v-else>--</dd> 151 </dl> 152 </b-col> 153 <b-col class="text-nowrap"> 154 <b-button @click="downloadEntry(item.additionalDataUri)"> 155 <icon-download />{{ $t('pageEventLogs.additionalDataUri') }} 156 </b-button> 157 </b-col> 158 </b-row> 159 </b-container> 160 </template> 161 162 <!-- Severity column --> 163 <template #cell(severity)="{ value }"> 164 <status-icon v-if="value" :status="statusIcon(value)" /> 165 {{ value }} 166 </template> 167 <!-- Date column --> 168 <template #cell(date)="{ value }"> 169 <p class="mb-0">{{ value }}</p> 170 <p class="mb-0">{{ value }}</p> 171 </template> 172 173 <!-- Status column --> 174 <template #cell(status)="row"> 175 <b-form-checkbox 176 v-model="row.item.status" 177 name="switch" 178 switch 179 @change="changelogStatus(row.item)" 180 > 181 <span v-if="row.item.status"> 182 {{ $t('pageEventLogs.resolved') }} 183 </span> 184 <span v-else> {{ $t('pageEventLogs.unresolved') }} </span> 185 </b-form-checkbox> 186 </template> 187 <template #cell(filterByStatus)="{ value }"> 188 {{ value }} 189 </template> 190 191 <!-- Actions column --> 192 <template #cell(actions)="row"> 193 <table-row-action 194 v-for="(action, index) in row.item.actions" 195 :key="index" 196 :value="action.value" 197 :title="action.title" 198 :row-data="row.item" 199 :export-name="exportFileNameByDate('export')" 200 :data-test-id="`eventLogs-button-deleteRow-${row.index}`" 201 @click-table-action="onTableRowAction($event, row.item)" 202 > 203 <template #icon> 204 <icon-export v-if="action.value === 'export'" /> 205 <icon-trashcan v-if="action.value === 'delete'" /> 206 </template> 207 </table-row-action> 208 </template> 209 </b-table> 210 </b-col> 211 </b-row> 212 213 <!-- Table pagination --> 214 <b-row> 215 <b-col sm="6"> 216 <b-form-group 217 class="table-pagination-select" 218 :label="$t('global.table.itemsPerPage')" 219 label-for="pagination-items-per-page" 220 > 221 <b-form-select 222 id="pagination-items-per-page" 223 v-model="perPage" 224 :options="itemsPerPageOptions" 225 /> 226 </b-form-group> 227 </b-col> 228 <b-col sm="6"> 229 <b-pagination 230 v-model="currentPage" 231 first-number 232 last-number 233 :per-page="perPage" 234 :total-rows="getTotalRowCount(filteredRows)" 235 aria-controls="table-event-logs" 236 /> 237 </b-col> 238 </b-row> 239 </b-container> 240</template> 241 242<script> 243import IconDelete from '@carbon/icons-vue/es/trash-can/20'; 244import IconTrashcan from '@carbon/icons-vue/es/trash-can/20'; 245import IconExport from '@carbon/icons-vue/es/document--export/20'; 246import IconChevron from '@carbon/icons-vue/es/chevron--down/20'; 247import IconDownload from '@carbon/icons-vue/es/download/20'; 248import { omit } from 'lodash'; 249 250import PageTitle from '@/components/Global/PageTitle'; 251import StatusIcon from '@/components/Global/StatusIcon'; 252import Search from '@/components/Global/Search'; 253import TableCellCount from '@/components/Global/TableCellCount'; 254import TableDateFilter from '@/components/Global/TableDateFilter'; 255import TableFilter from '@/components/Global/TableFilter'; 256import TableRowAction from '@/components/Global/TableRowAction'; 257import TableToolbar from '@/components/Global/TableToolbar'; 258import TableToolbarExport from '@/components/Global/TableToolbarExport'; 259 260import LoadingBarMixin from '@/components/Mixins/LoadingBarMixin'; 261import TableFilterMixin from '@/components/Mixins/TableFilterMixin'; 262import BVPaginationMixin, { 263 currentPage, 264 perPage, 265 itemsPerPageOptions, 266} from '@/components/Mixins/BVPaginationMixin'; 267import BVTableSelectableMixin, { 268 selectedRows, 269 tableHeaderCheckboxModel, 270 tableHeaderCheckboxIndeterminate, 271} from '@/components/Mixins/BVTableSelectableMixin'; 272import BVToastMixin from '@/components/Mixins/BVToastMixin'; 273import DataFormatterMixin from '@/components/Mixins/DataFormatterMixin'; 274import TableSortMixin from '@/components/Mixins/TableSortMixin'; 275import TableRowExpandMixin, { 276 expandRowLabel, 277} from '@/components/Mixins/TableRowExpandMixin'; 278import SearchFilterMixin, { 279 searchFilter, 280} from '@/components/Mixins/SearchFilterMixin'; 281 282export default { 283 components: { 284 IconDelete, 285 IconExport, 286 IconTrashcan, 287 IconChevron, 288 IconDownload, 289 PageTitle, 290 Search, 291 StatusIcon, 292 TableCellCount, 293 TableFilter, 294 TableRowAction, 295 TableToolbar, 296 TableToolbarExport, 297 TableDateFilter, 298 }, 299 mixins: [ 300 BVPaginationMixin, 301 BVTableSelectableMixin, 302 BVToastMixin, 303 LoadingBarMixin, 304 TableFilterMixin, 305 DataFormatterMixin, 306 TableSortMixin, 307 TableRowExpandMixin, 308 SearchFilterMixin, 309 ], 310 beforeRouteLeave(to, from, next) { 311 // Hide loader if the user navigates to another page 312 // before request is fulfilled. 313 this.hideLoader(); 314 next(); 315 }, 316 data() { 317 return { 318 isBusy: true, 319 fields: [ 320 { 321 key: 'expandRow', 322 label: '', 323 tdClass: 'table-row-expand', 324 }, 325 { 326 key: 'checkbox', 327 sortable: false, 328 }, 329 { 330 key: 'id', 331 label: this.$t('pageEventLogs.table.id'), 332 sortable: true, 333 }, 334 { 335 key: 'severity', 336 label: this.$t('pageEventLogs.table.severity'), 337 sortable: true, 338 tdClass: 'text-nowrap', 339 }, 340 { 341 key: 'date', 342 label: this.$t('pageEventLogs.table.date'), 343 sortable: true, 344 tdClass: 'text-nowrap', 345 }, 346 { 347 key: 'description', 348 label: this.$t('pageEventLogs.table.description'), 349 tdClass: 'text-break', 350 }, 351 process.env.VUE_APP_EVENT_LOGS_TOGGLE_BUTTON_DISABLED === 'true' 352 ? {} 353 : { 354 key: 'status', 355 label: this.$t('pageEventLogs.table.status'), 356 }, 357 { 358 key: 'actions', 359 sortable: false, 360 label: '', 361 tdClass: 'text-right text-nowrap', 362 }, 363 ], 364 tableFilters: 365 process.env.VUE_APP_EVENT_LOGS_TOGGLE_BUTTON_DISABLED === 'true' 366 ? [ 367 { 368 key: 'severity', 369 label: this.$t('pageEventLogs.table.severity'), 370 values: ['OK', 'Warning', 'Critical'], 371 }, 372 ] 373 : [ 374 { 375 key: 'severity', 376 label: this.$t('pageEventLogs.table.severity'), 377 values: ['OK', 'Warning', 'Critical'], 378 }, 379 { 380 key: 'filterByStatus', 381 label: this.$t('pageEventLogs.table.status'), 382 values: ['Resolved', 'Unresolved'], 383 }, 384 ], 385 expandRowLabel, 386 activeFilters: [], 387 batchActions: 388 process.env.VUE_APP_EVENT_LOGS_DELETE_BUTTON_DISABLED === 'true' 389 ? [] 390 : [ 391 { 392 value: 'delete', 393 label: this.$t('global.action.delete'), 394 }, 395 ], 396 currentPage: currentPage, 397 filterStartDate: null, 398 filterEndDate: null, 399 itemsPerPageOptions: itemsPerPageOptions, 400 perPage: perPage, 401 searchFilter: searchFilter, 402 searchTotalFilteredRows: 0, 403 selectedRows: selectedRows, 404 tableHeaderCheckboxModel: tableHeaderCheckboxModel, 405 tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate, 406 hideToggle: 407 process.env.VUE_APP_EVENT_LOGS_TOGGLE_BUTTON_DISABLED === 'true', 408 hideDelete: 409 process.env.VUE_APP_EVENT_LOGS_DELETE_BUTTON_DISABLED === 'true', 410 }; 411 }, 412 computed: { 413 href() { 414 return `data:text/json;charset=utf-8,${this.exportAllLogs()}`; 415 }, 416 filteredRows() { 417 return this.searchFilter 418 ? this.searchTotalFilteredRows 419 : this.filteredLogs.length; 420 }, 421 allLogs() { 422 return this.$store.getters['eventLog/allEvents'].map((event) => { 423 return { 424 ...event, 425 actions: this.hideDelete 426 ? [ 427 { 428 value: 'export', 429 title: this.$t('global.action.export'), 430 }, 431 ] 432 : [ 433 { 434 value: 'export', 435 title: this.$t('global.action.export'), 436 }, 437 { 438 value: 'delete', 439 title: this.$t('global.action.delete'), 440 }, 441 ], 442 }; 443 }); 444 }, 445 batchExportData() { 446 return this.selectedRows.map((row) => omit(row, 'actions')); 447 }, 448 filteredLogsByDate() { 449 return this.getFilteredTableDataByDate( 450 this.allLogs, 451 this.filterStartDate, 452 this.filterEndDate, 453 ); 454 }, 455 filteredLogs() { 456 return this.getFilteredTableData( 457 this.filteredLogsByDate, 458 this.activeFilters, 459 ); 460 }, 461 }, 462 created() { 463 this.startLoader(); 464 this.$store.dispatch('eventLog/getEventLogData').finally(() => { 465 this.endLoader(); 466 this.isBusy = false; 467 }); 468 }, 469 methods: { 470 downloadEntry(uri) { 471 let filename = uri?.split('LogServices/')?.[1]; 472 filename.replace(RegExp('/', 'g'), '_'); 473 this.$store 474 .dispatch('eventLog/downloadEntry', uri) 475 .then((blob) => { 476 const link = document.createElement('a'); 477 link.href = URL.createObjectURL(blob); 478 link.download = filename; 479 link.click(); 480 URL.revokeObjectURL(link.href); 481 }) 482 .catch(({ message }) => this.errorToast(message)); 483 }, 484 changelogStatus(row) { 485 this.$store 486 .dispatch('eventLog/updateEventLogStatus', { 487 uri: row.uri, 488 status: row.status, 489 }) 490 .then((success) => { 491 this.successToast(success); 492 }) 493 .catch(({ message }) => this.errorToast(message)); 494 }, 495 deleteAllLogs() { 496 this.$bvModal 497 .msgBoxConfirm(this.$t('pageEventLogs.modal.deleteAllMessage'), { 498 title: this.$t('pageEventLogs.modal.deleteAllTitle'), 499 okTitle: this.$t('global.action.delete'), 500 okVariant: 'danger', 501 cancelTitle: this.$t('global.action.cancel'), 502 autoFocusButton: 'cancel', 503 }) 504 .then((deleteConfirmed) => { 505 if (deleteConfirmed) { 506 this.$store 507 .dispatch('eventLog/deleteAllEventLogs', this.allLogs) 508 .then((message) => this.successToast(message)) 509 .catch(({ message }) => this.errorToast(message)); 510 } 511 }); 512 }, 513 deleteLogs(uris) { 514 this.$store 515 .dispatch('eventLog/deleteEventLogs', uris) 516 .then((messages) => { 517 messages.forEach(({ type, message }) => { 518 if (type === 'success') { 519 this.successToast(message); 520 } else if (type === 'error') { 521 this.errorToast(message); 522 } 523 }); 524 }); 525 }, 526 exportAllLogs() { 527 { 528 return this.$store.getters['eventLog/allEvents'].map((eventLogs) => { 529 const allEventLogsString = JSON.stringify(eventLogs); 530 return allEventLogsString; 531 }); 532 } 533 }, 534 onFilterChange({ activeFilters }) { 535 this.activeFilters = activeFilters; 536 }, 537 onSortCompare(a, b, key) { 538 if (key === 'severity') { 539 return this.sortStatus(a, b, key); 540 } 541 }, 542 onTableRowAction(action, { uri }) { 543 if (action === 'delete') { 544 this.$bvModal 545 .msgBoxConfirm(this.$tc('pageEventLogs.modal.deleteMessage'), { 546 title: this.$tc('pageEventLogs.modal.deleteTitle'), 547 okTitle: this.$t('global.action.delete'), 548 cancelTitle: this.$t('global.action.cancel'), 549 autoFocusButton: 'ok', 550 }) 551 .then((deleteConfirmed) => { 552 if (deleteConfirmed) this.deleteLogs([uri]); 553 }); 554 } 555 }, 556 onBatchAction(action) { 557 if (action === 'delete') { 558 const uris = this.selectedRows.map((row) => row.uri); 559 this.$bvModal 560 .msgBoxConfirm( 561 this.$tc( 562 'pageEventLogs.modal.deleteMessage', 563 this.selectedRows.length, 564 ), 565 { 566 title: this.$tc( 567 'pageEventLogs.modal.deleteTitle', 568 this.selectedRows.length, 569 ), 570 okTitle: this.$t('global.action.delete'), 571 cancelTitle: this.$t('global.action.cancel'), 572 autoFocusButton: 'ok', 573 }, 574 ) 575 .then((deleteConfirmed) => { 576 if (deleteConfirmed) { 577 if (this.selectedRows.length === this.allLogs.length) { 578 this.$store 579 .dispatch( 580 'eventLog/deleteAllEventLogs', 581 this.selectedRows.length, 582 ) 583 .then(() => { 584 this.successToast( 585 this.$tc( 586 'pageEventLogs.toast.successDelete', 587 uris.length, 588 ), 589 ); 590 }) 591 .catch(({ message }) => this.errorToast(message)); 592 } else { 593 this.deleteLogs(uris); 594 } 595 } 596 }); 597 } 598 }, 599 onChangeDateTimeFilter({ fromDate, toDate }) { 600 this.filterStartDate = fromDate; 601 this.filterEndDate = toDate; 602 }, 603 onFiltered(filteredItems) { 604 this.searchTotalFilteredRows = filteredItems.length; 605 }, 606 // Create export file name based on date 607 exportFileNameByDate(value) { 608 let date = new Date(); 609 date = 610 date.toISOString().slice(0, 10) + 611 '_' + 612 date.toString().split(':').join('-').split(' ')[4]; 613 let fileName; 614 if (value === 'export') { 615 fileName = 'event_log_'; 616 } else { 617 fileName = 'all_event_logs_'; 618 } 619 return fileName + date; 620 }, 621 resolveLogs() { 622 this.$store 623 .dispatch('eventLog/resolveEventLogs', this.selectedRows) 624 .then((messages) => { 625 messages.forEach(({ type, message }) => { 626 if (type === 'success') { 627 this.successToast(message); 628 } else if (type === 'error') { 629 this.errorToast(message); 630 } 631 }); 632 }); 633 }, 634 unresolveLogs() { 635 this.$store 636 .dispatch('eventLog/unresolveEventLogs', this.selectedRows) 637 .then((messages) => { 638 messages.forEach(({ type, message }) => { 639 if (type === 'success') { 640 this.successToast(message); 641 } else if (type === 'error') { 642 this.errorToast(message); 643 } 644 }); 645 }); 646 }, 647 }, 648}; 649</script> 650