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