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 variant="primary" @click="resolveLogs"> 54 {{ $t('pageEventLogs.resolve') }} 55 </b-button> 56 <b-button variant="primary" @click="unresolveLogs"> 57 {{ $t('pageEventLogs.unresolve') }} 58 </b-button> 59 <table-toolbar-export 60 :data="batchExportData" 61 :file-name="exportFileNameByDate()" 62 /> 63 </template> 64 </table-toolbar> 65 <b-table 66 id="table-event-logs" 67 ref="table" 68 responsive="md" 69 selectable 70 no-select-on-click 71 sort-icon-left 72 hover 73 no-sort-reset 74 sort-desc 75 show-empty 76 sort-by="id" 77 :fields="fields" 78 :items="filteredLogs" 79 :sort-compare="onSortCompare" 80 :empty-text="$t('global.table.emptyMessage')" 81 :empty-filtered-text="$t('global.table.emptySearchMessage')" 82 :per-page="perPage" 83 :current-page="currentPage" 84 :filter="searchFilter" 85 :busy="isBusy" 86 @filtered="onFiltered" 87 @row-selected="onRowSelected($event, filteredLogs.length)" 88 > 89 <!-- Checkbox column --> 90 <template #head(checkbox)> 91 <b-form-checkbox 92 v-model="tableHeaderCheckboxModel" 93 data-test-id="eventLogs-checkbox-selectAll" 94 :indeterminate="tableHeaderCheckboxIndeterminate" 95 @change="onChangeHeaderCheckbox($refs.table)" 96 > 97 <span class="sr-only">{{ $t('global.table.selectAll') }}</span> 98 </b-form-checkbox> 99 </template> 100 <template #cell(checkbox)="row"> 101 <b-form-checkbox 102 v-model="row.rowSelected" 103 :data-test-id="`eventLogs-checkbox-selectRow-${row.index}`" 104 @change="toggleSelectRow($refs.table, row.index)" 105 > 106 <span class="sr-only">{{ $t('global.table.selectItem') }}</span> 107 </b-form-checkbox> 108 </template> 109 110 <!-- Expand chevron icon --> 111 <template #cell(expandRow)="row"> 112 <b-button 113 variant="link" 114 :aria-label="expandRowLabel" 115 :title="expandRowLabel" 116 class="btn-icon-only" 117 @click="toggleRowDetails(row)" 118 > 119 <icon-chevron /> 120 </b-button> 121 </template> 122 123 <template #row-details="{ item }"> 124 <b-container fluid> 125 <b-row> 126 <b-col> 127 <dl> 128 <!-- Name --> 129 <dt>{{ $t('pageEventLogs.table.name') }}:</dt> 130 <dd>{{ dataFormatter(item.name) }}</dd> 131 </dl> 132 <dl> 133 <!-- Type --> 134 <dt>{{ $t('pageEventLogs.table.type') }}:</dt> 135 <dd>{{ dataFormatter(item.type) }}</dd> 136 </dl> 137 </b-col> 138 <b-col> 139 <dl> 140 <!-- Modified date --> 141 <dt>{{ $t('pageEventLogs.table.modifiedDate') }}:</dt> 142 <dd v-if="item.modifiedDate"> 143 {{ item.modifiedDate | formatDate }} 144 {{ item.modifiedDate | formatTime }} 145 </dd> 146 <dd v-else>--</dd> 147 </dl> 148 </b-col> 149 <b-col class="text-nowrap"> 150 <b-button 151 class="btn btn-secondary float-right" 152 :href="item.additionalDataUri" 153 target="_blank" 154 > 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 | formatDate }}</p> 170 <p class="mb-0">{{ value | formatTime }}</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 { 352 key: 'status', 353 label: this.$t('pageEventLogs.table.status'), 354 }, 355 { 356 key: 'actions', 357 sortable: false, 358 label: '', 359 tdClass: 'text-right text-nowrap', 360 }, 361 ], 362 tableFilters: [ 363 { 364 key: 'severity', 365 label: this.$t('pageEventLogs.table.severity'), 366 values: ['OK', 'Warning', 'Critical'], 367 }, 368 { 369 key: 'filterByStatus', 370 label: this.$t('pageEventLogs.table.status'), 371 values: ['Resolved', 'Unresolved'], 372 }, 373 ], 374 expandRowLabel, 375 activeFilters: [], 376 batchActions: 377 process.env.VUE_APP_EVENT_LOGS_DELETE_BUTTON_DISABLED === 'true' 378 ? [] 379 : [ 380 { 381 value: 'delete', 382 label: this.$t('global.action.delete'), 383 }, 384 ], 385 currentPage: currentPage, 386 filterStartDate: null, 387 filterEndDate: null, 388 itemsPerPageOptions: itemsPerPageOptions, 389 perPage: perPage, 390 searchFilter: searchFilter, 391 searchTotalFilteredRows: 0, 392 selectedRows: selectedRows, 393 tableHeaderCheckboxModel: tableHeaderCheckboxModel, 394 tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate, 395 hideDelete: 396 process.env.VUE_APP_EVENT_LOGS_DELETE_BUTTON_DISABLED === 'true', 397 }; 398 }, 399 computed: { 400 href() { 401 return `data:text/json;charset=utf-8,${this.exportAllLogs()}`; 402 }, 403 filteredRows() { 404 return this.searchFilter 405 ? this.searchTotalFilteredRows 406 : this.filteredLogs.length; 407 }, 408 allLogs() { 409 return this.$store.getters['eventLog/allEvents'].map((event) => { 410 return { 411 ...event, 412 actions: this.hideDelete 413 ? [ 414 { 415 value: 'export', 416 title: this.$t('global.action.export'), 417 }, 418 ] 419 : [ 420 { 421 value: 'export', 422 title: this.$t('global.action.export'), 423 }, 424 { 425 value: 'delete', 426 title: this.$t('global.action.delete'), 427 }, 428 ], 429 }; 430 }); 431 }, 432 batchExportData() { 433 return this.selectedRows.map((row) => omit(row, 'actions')); 434 }, 435 filteredLogsByDate() { 436 return this.getFilteredTableDataByDate( 437 this.allLogs, 438 this.filterStartDate, 439 this.filterEndDate 440 ); 441 }, 442 filteredLogs() { 443 return this.getFilteredTableData( 444 this.filteredLogsByDate, 445 this.activeFilters 446 ); 447 }, 448 }, 449 created() { 450 this.startLoader(); 451 this.$store.dispatch('eventLog/getEventLogData').finally(() => { 452 this.endLoader(); 453 this.isBusy = false; 454 }); 455 }, 456 methods: { 457 changelogStatus(row) { 458 this.$store 459 .dispatch('eventLog/updateEventLogStatus', { 460 uri: row.uri, 461 status: row.status, 462 }) 463 .then((success) => { 464 this.successToast(success); 465 }) 466 .catch(({ message }) => this.errorToast(message)); 467 }, 468 deleteAllLogs() { 469 this.$bvModal 470 .msgBoxConfirm(this.$t('pageEventLogs.modal.deleteAllMessage'), { 471 title: this.$t('pageEventLogs.modal.deleteAllTitle'), 472 okTitle: this.$t('global.action.delete'), 473 okVariant: 'danger', 474 cancelTitle: this.$t('global.action.cancel'), 475 }) 476 .then((deleteConfirmed) => { 477 if (deleteConfirmed) { 478 this.$store 479 .dispatch('eventLog/deleteAllEventLogs', this.allLogs) 480 .then((message) => this.successToast(message)) 481 .catch(({ message }) => this.errorToast(message)); 482 } 483 }); 484 }, 485 deleteLogs(uris) { 486 this.$store 487 .dispatch('eventLog/deleteEventLogs', uris) 488 .then((messages) => { 489 messages.forEach(({ type, message }) => { 490 if (type === 'success') { 491 this.successToast(message); 492 } else if (type === 'error') { 493 this.errorToast(message); 494 } 495 }); 496 }); 497 }, 498 exportAllLogs() { 499 { 500 return this.$store.getters['eventLog/allEvents'].map((eventLogs) => { 501 const allEventLogsString = JSON.stringify(eventLogs); 502 return allEventLogsString; 503 }); 504 } 505 }, 506 onFilterChange({ activeFilters }) { 507 this.activeFilters = activeFilters; 508 }, 509 onSortCompare(a, b, key) { 510 if (key === 'severity') { 511 return this.sortStatus(a, b, key); 512 } 513 }, 514 onTableRowAction(action, { uri }) { 515 if (action === 'delete') { 516 this.$bvModal 517 .msgBoxConfirm(this.$tc('pageEventLogs.modal.deleteMessage'), { 518 title: this.$tc('pageEventLogs.modal.deleteTitle'), 519 okTitle: this.$t('global.action.delete'), 520 cancelTitle: this.$t('global.action.cancel'), 521 }) 522 .then((deleteConfirmed) => { 523 if (deleteConfirmed) this.deleteLogs([uri]); 524 }); 525 } 526 }, 527 onBatchAction(action) { 528 if (action === 'delete') { 529 const uris = this.selectedRows.map((row) => row.uri); 530 this.$bvModal 531 .msgBoxConfirm( 532 this.$tc( 533 'pageEventLogs.modal.deleteMessage', 534 this.selectedRows.length 535 ), 536 { 537 title: this.$tc( 538 'pageEventLogs.modal.deleteTitle', 539 this.selectedRows.length 540 ), 541 okTitle: this.$t('global.action.delete'), 542 cancelTitle: this.$t('global.action.cancel'), 543 } 544 ) 545 .then((deleteConfirmed) => { 546 if (deleteConfirmed) { 547 if (this.selectedRows.length === this.allLogs.length) { 548 this.$store 549 .dispatch( 550 'eventLog/deleteAllEventLogs', 551 this.selectedRows.length 552 ) 553 .then(() => { 554 this.successToast( 555 this.$tc('pageEventLogs.toast.successDelete', uris.length) 556 ); 557 }) 558 .catch(({ message }) => this.errorToast(message)); 559 } else { 560 this.deleteLogs(uris); 561 } 562 } 563 }); 564 } 565 }, 566 onChangeDateTimeFilter({ fromDate, toDate }) { 567 this.filterStartDate = fromDate; 568 this.filterEndDate = toDate; 569 }, 570 onFiltered(filteredItems) { 571 this.searchTotalFilteredRows = filteredItems.length; 572 }, 573 // Create export file name based on date 574 exportFileNameByDate(value) { 575 let date = new Date(); 576 date = 577 date.toISOString().slice(0, 10) + 578 '_' + 579 date.toString().split(':').join('-').split(' ')[4]; 580 let fileName; 581 if (value === 'export') { 582 fileName = 'event_log_'; 583 } else { 584 fileName = 'all_event_logs_'; 585 } 586 return fileName + date; 587 }, 588 resolveLogs() { 589 this.$store 590 .dispatch('eventLog/resolveEventLogs', this.selectedRows) 591 .then((messages) => { 592 messages.forEach(({ type, message }) => { 593 if (type === 'success') { 594 this.successToast(message); 595 } else if (type === 'error') { 596 this.errorToast(message); 597 } 598 }); 599 }); 600 }, 601 unresolveLogs() { 602 this.$store 603 .dispatch('eventLog/unresolveEventLogs', this.selectedRows) 604 .then((messages) => { 605 messages.forEach(({ type, message }) => { 606 if (type === 'success') { 607 this.successToast(message); 608 } else if (type === 'error') { 609 this.errorToast(message); 610 } 611 }); 612 }); 613 }, 614 }, 615}; 616</script> 617