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 { 378 value: 'delete', 379 label: this.$t('global.action.delete'), 380 }, 381 ], 382 currentPage: currentPage, 383 filterStartDate: null, 384 filterEndDate: null, 385 itemsPerPageOptions: itemsPerPageOptions, 386 perPage: perPage, 387 searchFilter: searchFilter, 388 searchTotalFilteredRows: 0, 389 selectedRows: selectedRows, 390 tableHeaderCheckboxModel: tableHeaderCheckboxModel, 391 tableHeaderCheckboxIndeterminate: tableHeaderCheckboxIndeterminate, 392 }; 393 }, 394 computed: { 395 href() { 396 return `data:text/json;charset=utf-8,${this.exportAllLogs()}`; 397 }, 398 filteredRows() { 399 return this.searchFilter 400 ? this.searchTotalFilteredRows 401 : this.filteredLogs.length; 402 }, 403 allLogs() { 404 return this.$store.getters['eventLog/allEvents'].map((event) => { 405 return { 406 ...event, 407 actions: [ 408 { 409 value: 'export', 410 title: this.$t('global.action.export'), 411 }, 412 { 413 value: 'delete', 414 title: this.$t('global.action.delete'), 415 }, 416 ], 417 }; 418 }); 419 }, 420 batchExportData() { 421 return this.selectedRows.map((row) => omit(row, 'actions')); 422 }, 423 filteredLogsByDate() { 424 return this.getFilteredTableDataByDate( 425 this.allLogs, 426 this.filterStartDate, 427 this.filterEndDate 428 ); 429 }, 430 filteredLogs() { 431 return this.getFilteredTableData( 432 this.filteredLogsByDate, 433 this.activeFilters 434 ); 435 }, 436 }, 437 created() { 438 this.startLoader(); 439 this.$store.dispatch('eventLog/getEventLogData').finally(() => { 440 this.endLoader(); 441 this.isBusy = false; 442 }); 443 }, 444 methods: { 445 changelogStatus(row) { 446 this.$store 447 .dispatch('eventLog/updateEventLogStatus', { 448 uri: row.uri, 449 status: row.status, 450 }) 451 .then((success) => { 452 this.successToast(success); 453 }) 454 .catch(({ message }) => this.errorToast(message)); 455 }, 456 deleteAllLogs() { 457 this.$bvModal 458 .msgBoxConfirm(this.$t('pageEventLogs.modal.deleteAllMessage'), { 459 title: this.$t('pageEventLogs.modal.deleteAllTitle'), 460 okTitle: this.$t('global.action.delete'), 461 okVariant: 'danger', 462 cancelTitle: this.$t('global.action.cancel'), 463 }) 464 .then((deleteConfirmed) => { 465 if (deleteConfirmed) { 466 this.$store 467 .dispatch('eventLog/deleteAllEventLogs', this.allLogs) 468 .then((message) => this.successToast(message)) 469 .catch(({ message }) => this.errorToast(message)); 470 } 471 }); 472 }, 473 deleteLogs(uris) { 474 this.$store 475 .dispatch('eventLog/deleteEventLogs', uris) 476 .then((messages) => { 477 messages.forEach(({ type, message }) => { 478 if (type === 'success') { 479 this.successToast(message); 480 } else if (type === 'error') { 481 this.errorToast(message); 482 } 483 }); 484 }); 485 }, 486 exportAllLogs() { 487 { 488 return this.$store.getters['eventLog/allEvents'].map((eventLogs) => { 489 const allEventLogsString = JSON.stringify(eventLogs); 490 return allEventLogsString; 491 }); 492 } 493 }, 494 onFilterChange({ activeFilters }) { 495 this.activeFilters = activeFilters; 496 }, 497 onSortCompare(a, b, key) { 498 if (key === 'severity') { 499 return this.sortStatus(a, b, key); 500 } 501 }, 502 onTableRowAction(action, { uri }) { 503 if (action === 'delete') { 504 this.$bvModal 505 .msgBoxConfirm(this.$tc('pageEventLogs.modal.deleteMessage'), { 506 title: this.$tc('pageEventLogs.modal.deleteTitle'), 507 okTitle: this.$t('global.action.delete'), 508 cancelTitle: this.$t('global.action.cancel'), 509 }) 510 .then((deleteConfirmed) => { 511 if (deleteConfirmed) this.deleteLogs([uri]); 512 }); 513 } 514 }, 515 onBatchAction(action) { 516 if (action === 'delete') { 517 const uris = this.selectedRows.map((row) => row.uri); 518 this.$bvModal 519 .msgBoxConfirm( 520 this.$tc( 521 'pageEventLogs.modal.deleteMessage', 522 this.selectedRows.length 523 ), 524 { 525 title: this.$tc( 526 'pageEventLogs.modal.deleteTitle', 527 this.selectedRows.length 528 ), 529 okTitle: this.$t('global.action.delete'), 530 cancelTitle: this.$t('global.action.cancel'), 531 } 532 ) 533 .then((deleteConfirmed) => { 534 if (deleteConfirmed) { 535 if (this.selectedRows.length === this.allLogs.length) { 536 this.$store 537 .dispatch( 538 'eventLog/deleteAllEventLogs', 539 this.selectedRows.length 540 ) 541 .then(() => { 542 this.successToast( 543 this.$tc('pageEventLogs.toast.successDelete', uris.length) 544 ); 545 }) 546 .catch(({ message }) => this.errorToast(message)); 547 } else { 548 this.deleteLogs(uris); 549 } 550 } 551 }); 552 } 553 }, 554 onChangeDateTimeFilter({ fromDate, toDate }) { 555 this.filterStartDate = fromDate; 556 this.filterEndDate = toDate; 557 }, 558 onFiltered(filteredItems) { 559 this.searchTotalFilteredRows = filteredItems.length; 560 }, 561 // Create export file name based on date 562 exportFileNameByDate(value) { 563 let date = new Date(); 564 date = 565 date.toISOString().slice(0, 10) + 566 '_' + 567 date.toString().split(':').join('-').split(' ')[4]; 568 let fileName; 569 if (value === 'export') { 570 fileName = 'event_log_'; 571 } else { 572 fileName = 'all_event_logs_'; 573 } 574 return fileName + date; 575 }, 576 resolveLogs() { 577 this.$store 578 .dispatch('eventLog/resolveEventLogs', this.selectedRows) 579 .then((messages) => { 580 messages.forEach(({ type, message }) => { 581 if (type === 'success') { 582 this.successToast(message); 583 } else if (type === 'error') { 584 this.errorToast(message); 585 } 586 }); 587 }); 588 }, 589 unresolveLogs() { 590 this.$store 591 .dispatch('eventLog/unresolveEventLogs', this.selectedRows) 592 .then((messages) => { 593 messages.forEach(({ type, message }) => { 594 if (type === 'success') { 595 this.successToast(message); 596 } else if (type === 'error') { 597 this.errorToast(message); 598 } 599 }); 600 }); 601 }, 602 }, 603}; 604</script> 605