Repository: metron Updated Branches: refs/heads/master c18faaa94 -> 529ea6cc4
METRON-1182 Refactor Code in alert list to accommodate new view types (iraghumitra via merrimanr) closes apache/metron#756 Project: http://git-wip-us.apache.org/repos/asf/metron/repo Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/529ea6cc Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/529ea6cc Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/529ea6cc Branch: refs/heads/master Commit: 529ea6cc41051bb7bd0b94ad471dc124d0e00e61 Parents: c18faaa Author: iraghumitra <[email protected]> Authored: Thu Sep 21 12:49:28 2017 -0500 Committer: merrimanr <[email protected]> Committed: Thu Sep 21 12:49:28 2017 -0500 ---------------------------------------------------------------------- .../alerts-list/alerts-list.component.html | 43 ++--- .../alerts-list/alerts-list.component.scss | 9 - .../alerts/alerts-list/alerts-list.component.ts | 139 ++++----------- .../alerts/alerts-list/alerts-list.module.ts | 5 +- .../src/app/alerts/alerts-list/query-builder.ts | 12 +- .../table-view/table-view.component.html | 42 +++++ .../table-view/table-view.component.scss | 27 +++ .../table-view/table-view.component.spec.ts | 25 +++ .../table-view/table-view.component.ts | 172 +++++++++++++++++++ 9 files changed, 323 insertions(+), 151 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metron/blob/529ea6cc/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html index 3707e9a..c31b9f8 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html @@ -36,15 +36,15 @@ </div> <div class="mrow"> <div class="col-md-9 px-0"> - <span class="col-form-label-lg"> Alerts ({{alerts.length}} of {{pagingData.total}}) </span> + <span class="col-form-label-lg"> Alerts ({{searchResponse.total}}) </span> </div> <div class="col-md-3 px-0"> <div class="pull-right" style="position: relative; display: block;"> <div class="btn settings"> <i #settingsIcon class="fa fa-sliders" aria-hidden="true"></i> </div> - <app-configure-rows [srcElement]="settingsIcon" [tableMetaData]="tableMetaData" [(interval)]="refreshInterval" [(size)]="pagingData.size" (configRowsChange)="onConfigRowsChange()" > </app-configure-rows> - <div class="btn pause-play" (click)="onPausePlay()"> + <app-configure-rows [srcElement]="settingsIcon" [tableMetaData]="tableMetaData" [(interval)]="refreshInterval" [(size)]="tableMetaData.size" (configRowsChange)="onConfigRowsChange()" > </app-configure-rows> + <div class="btn pause-play" (click)="onPausePlay()"> <i *ngIf="!pauseRefresh" class="fa fa-pause" aria-hidden="true"></i> <i *ngIf="pauseRefresh" class="fa fa-play" aria-hidden="true"></i> </div> @@ -65,35 +65,14 @@ <div class="container-fluid nav-content"> <div class="row"> <div class="col-sm-12 pl-0"> - <div class="table-wrapper"> - <table class="table table-sm" metron-config-table [data]="alerts" [cellSelectable]="true" (onSort)="onSort($event)" style="white-space: nowrap;" (window:resize)="onResize()" #table> - <thead> - <tr> - <th style="width:55px"> <metron-config-sorter [type]="'number'" [sortBy]="threatScoreFieldName"> Score </metron-config-sorter> </th> - <th *ngFor="let column of alertsColumnsToDisplay" [id]="column.name"> <metron-config-sorter [type]="column.type" [sortBy]="column.name" title="{{column.name}}"> {{ column.name | columnNameTranslate | centerEllipses:15 }}</metron-config-sorter> </th> - <th style="width:25px"><i class="fa fa-cog configure-table-icon" (click)="showConfigureTable()"></i></th> - <th style="width:25px"><input id="select-deselect-all" class="fontawesome-checkbox" type="checkbox" (click)="selectAllRows($event)"><label for="select-deselect-all"></label></th> - </tr> - </thead> - <tbody> - <tr *ngFor="let alert of alerts" (click)="showDetails($event, alert)" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1}"> - <td (click)="onAddFilter(threatScoreFieldName, alert.source[threatScoreFieldName])"> - <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]"> <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }} </a> </div> - </td> - <td *ngFor="let column of alertsColumnsToDisplay" #cell> - <a (click)="onAddFilter(column.name, getValue(alert, column, false))" title="{{getValue(alert, column, true)}}" style="color:#689AA9">{{ getValue(alert, column, true) | centerEllipses:20:cell }}</a> - </td> - <td></td> - <td><input id="{{ alert.id }}" class="fontawesome-checkbox" type="checkbox" name="{{alert.id}}" (click)="selectRow($event, alert)" [checked]="selectedAlerts.indexOf(alert) != -1"><label attr.for="{{ alert.id }}"></label></td> - </tr> - </tbody> - </table> - </div> - <div clas="row"> - <div class="col-md-3 push-md-5"> - <metron-table-pagination [(pagingData)]="pagingData" (pageChange)="onPageChange()"> </metron-table-pagination> - </div> - </div> + <app-table-view #dataViewComponent + [queryBuilder]="queryBuilder" + [alertsColumnsToDisplay]="alertsColumnsToDisplay" + [(selectedAlerts)]="selectedAlerts" + (onResize)="onResize()" + (onAddFilter)="onAddFilter($event)" + (onShowDetails)="showDetails($event)" + (onShowConfigureTable)="showConfigureTable()"></app-table-view> </div> </div> </div> http://git-wip-us.apache.org/repos/asf/metron/blob/529ea6cc/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss index dd1bb99..6a26d3c 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss @@ -184,11 +184,6 @@ $searchbox-height: 42px; cursor: pointer; } -.configure-table-icon { - font-size: 16px; - cursor: pointer; -} - .pause-play { height: 38px; padding: 0px; @@ -232,10 +227,6 @@ $searchbox-height: 42px; } } -.table-wrapper { - min-height: calc(100vh - 250px); -} - .ace_editor { background: $mine-shaft-1; border: 1px solid $tundora; http://git-wip-us.apache.org/repos/asf/metron/blob/529ea6cc/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts index 72046fc..ae86002 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, OnInit, ViewChild, ElementRef, OnDestroy} from '@angular/core'; +import {Component, OnInit, ViewChild, ElementRef, OnDestroy, ChangeDetectorRef} from '@angular/core'; import {Router, NavigationStart} from '@angular/router'; import {Observable, Subscription} from 'rxjs/Rx'; @@ -26,9 +26,6 @@ import {ConfigureTableService} from '../../service/configure-table.service'; import {WorkflowService} from '../../service/workflow.service'; import {ClusterMetaDataService} from '../../service/cluster-metadata.service'; import {ColumnMetadata} from '../../model/column-metadata'; -import {SortEvent} from '../../shared/metron-table/metron-table.directive'; -import {Sort} from '../../utils/enums'; -import {Pagination} from '../../model/pagination'; import {SaveSearchService} from '../../service/save-search.service'; import {RefreshInterval} from '../configure-rows/configure-rows-enums'; import {SaveSearch} from '../../model/save-search'; @@ -37,6 +34,8 @@ import {MetronDialogBox, DialogType} from '../../shared/metron-dialog-box'; import {AlertSearchDirective} from '../../shared/directives/alert-search.directive'; import {SearchResponse} from '../../model/search-response'; import {ElasticsearchUtils} from '../../utils/elasticsearch-utils'; +import {TableViewComponent} from './table-view/table-view.component'; +import {Filter} from '../../model/filter'; @Component({ selector: 'app-alerts-list', @@ -49,18 +48,19 @@ export class AlertsListComponent implements OnInit, OnDestroy { alertsColumns: ColumnMetadata[] = []; alertsColumnsToDisplay: ColumnMetadata[] = []; selectedAlerts: Alert[] = []; - alerts: any[] = []; + alerts: Alert[] = []; + searchResponse: SearchResponse = new SearchResponse(); colNumberTimerId: number; refreshInterval = RefreshInterval.ONE_MIN; - refreshTimer: Subscription; pauseRefresh = false; lastPauseRefreshValue = false; threatScoreFieldName = 'threat:triage:score'; + refreshTimer: Subscription; @ViewChild('table') table: ElementRef; + @ViewChild('dataViewComponent') dataViewComponent: TableViewComponent; @ViewChild(AlertSearchDirective) alertSearchDirective: AlertSearchDirective; - pagingData = new Pagination(); tableMetaData = new TableMetadata(); queryBuilder: QueryBuilder = new QueryBuilder(); @@ -70,7 +70,8 @@ export class AlertsListComponent implements OnInit, OnDestroy { private workflowService: WorkflowService, private clusterMetaDataService: ClusterMetaDataService, private saveSearchService: SaveSearchService, - private metronDialogBox: MetronDialogBox) { + private metronDialogBox: MetronDialogBox, + private changeDetector: ChangeDetectorRef) { router.events.subscribe(event => { if (event instanceof NavigationStart && event.url === '/alerts-list') { this.selectedAlerts = []; @@ -116,16 +117,6 @@ export class AlertsListComponent implements OnInit, OnDestroy { }); } - formatValue(column: ColumnMetadata, returnValue: string) { - try { - if (column.name.endsWith(':ts') || column.name.endsWith('timestamp')) { - returnValue = new Date(parseInt(returnValue, 10)).toISOString().replace('T', ' ').slice(0, 19); - } - } catch (e) {} - - return returnValue; - } - getAlertColumnNames(resetPaginationForSearch: boolean) { Observable.forkJoin( this.configureTableService.getTableMetadata(), @@ -135,17 +126,6 @@ export class AlertsListComponent implements OnInit, OnDestroy { }); } - getCollapseComponentData(data: any) { - return { - getName: () => { - return Object.keys(data.aggregations)[0]; - }, - getData: () => { - return data.aggregations[Object.keys(data.aggregations)[0]].buckets; - }, - }; - } - getColumnNamesForQuery() { let fieldNames = this.alertsColumns.map(columnMetadata => columnMetadata.name); fieldNames = fieldNames.filter(name => !(name === 'id' || name === 'alert_status')); @@ -153,29 +133,6 @@ export class AlertsListComponent implements OnInit, OnDestroy { return fieldNames; } - getValue(alert: Alert, column: ColumnMetadata, formatData: boolean) { - let returnValue = ''; - try { - switch (column.name) { - case 'id': - returnValue = alert[column.name]; - break; - case 'alert_status': - returnValue = 'NEW'; - break; - default: - returnValue = alert.source[column.name]; - break; - } - } catch (e) {} - - if (formatData) { - returnValue = this.formatValue(column, returnValue); - } - - return returnValue; - } - ngOnDestroy() { this.tryStopPolling(); } @@ -198,8 +155,8 @@ export class AlertsListComponent implements OnInit, OnDestroy { return false; } - onAddFilter(field: string, value: string) { - this.queryBuilder.addOrUpdateFilter(field, value); + onAddFilter(filter: Filter) { + this.queryBuilder.addOrUpdateFilter(filter); this.search(); } @@ -208,9 +165,9 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.search(); } - onPageChange() { - this.queryBuilder.setFromAndSize(this.pagingData.from, this.pagingData.size); - this.search(false); + searchView(resetPaginationParams = true, pageSize: number = null) { + this.changeDetector.detectChanges(); + this.dataViewComponent.search(resetPaginationParams, pageSize); } onPausePlay() { @@ -227,13 +184,6 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.colNumberTimerId = setTimeout(() => { this.calcColumnsToDisplay(); }, 500); } - onSort(sortEvent: SortEvent) { - let sortOrder = (sortEvent.sortOrder === Sort.ASC ? 'asc' : 'desc'); - let sortBy = sortEvent.sortBy === 'id' ? '_uid' : sortEvent.sortBy; - this.queryBuilder.setSort(sortBy, sortOrder); - this.search(); - } - prepareColumnData(configuredColumns: ColumnMetadata[], defaultColumns: ColumnMetadata[]) { this.alertsColumns = (configuredColumns && configuredColumns.length > 0) ? configuredColumns : defaultColumns; this.queryBuilder.setFields(this.getColumnNamesForQuery()); @@ -242,7 +192,6 @@ export class AlertsListComponent implements OnInit, OnDestroy { prepareData(tableMetaData: TableMetadata, defaultColumns: ColumnMetadata[], resetPagination: boolean) { this.tableMetaData = tableMetaData; - this.pagingData.size = this.tableMetaData.size; this.refreshInterval = this.tableMetaData.refreshInterval; this.updateConfigRowsSettings(); @@ -287,32 +236,12 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.tryStartPolling(); } - selectAllRows($event) { - this.selectedAlerts = []; - if ($event.target.checked) { - this.selectedAlerts = this.alerts; - } - } - search(resetPaginationParams = true, savedSearch?: SaveSearch) { this.selectedAlerts = []; - if (resetPaginationParams) { - this.pagingData.from = 0; - this.queryBuilder.setFromAndSize(this.pagingData.from, this.pagingData.size); - } - - if (this.queryBuilder.query !== '*') { - if (!savedSearch) { - savedSearch = new SaveSearch(); - savedSearch.searchRequest = this.queryBuilder.searchRequest; - savedSearch.tableColumns = this.alertsColumns; - savedSearch.name = savedSearch.getDisplayString(); - } - - this.saveSearchService.saveAsRecentSearches(savedSearch).subscribe(() => {}); - } + this.saveCurrentSearch(savedSearch); + this.queryBuilder.setFromAndSize(0, 0); this.searchService.search(this.queryBuilder.searchRequest).subscribe(results => { this.setData(results); }, error => { @@ -320,20 +249,28 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.metronDialogBox.showConfirmationMessage(ElasticsearchUtils.extractESErrorMessage(error), DialogType.Error); }); + this.searchView(resetPaginationParams, this.tableMetaData.size); + this.tryStartPolling(); } - selectRow($event, alert: Alert) { - if ($event.target.checked) { - this.selectedAlerts.push(alert); - } else { - this.selectedAlerts.splice(this.selectedAlerts.indexOf(alert), 1); + saveCurrentSearch(savedSearch: SaveSearch) { + if (this.queryBuilder.query !== '*') { + if (!savedSearch) { + savedSearch = new SaveSearch(); + savedSearch.searchRequest = this.queryBuilder.searchRequest; + savedSearch.tableColumns = this.alertsColumns; + savedSearch.name = savedSearch.getDisplayString(); + } + + this.saveSearchService.saveAsRecentSearches(savedSearch).subscribe(() => { + }); } } setData(results: SearchResponse) { - this.alerts = results.results; - this.pagingData.total = results.total; + this.searchResponse = results; + this.alerts = results.results ? results.results : []; } showConfigureTable() { @@ -341,13 +278,11 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.router.navigateByUrl('/alerts-list(dialog:configure-table)'); } - showDetails($event, alert: Alert) { - if ($event.target.type !== 'checkbox' && $event.target.parentElement.firstChild.type !== 'checkbox' && $event.target.nodeName !== 'A') { - this.selectedAlerts = []; - this.selectedAlerts = [alert]; - this.saveRefreshState(); - this.router.navigateByUrl('/alerts-list(dialog:details/' + alert.source['source:type'] + '/' + alert.source.guid + ')'); - } + showDetails(alert: Alert) { + this.selectedAlerts = []; + this.selectedAlerts = [alert]; + this.saveRefreshState(); + this.router.navigateByUrl('/alerts-list(dialog:details/' + alert.source['source:type'] + '/' + alert.source.guid + ')'); } saveRefreshState() { @@ -372,6 +307,7 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.tryStopPolling(); this.refreshTimer = this.searchService.pollSearch(this.queryBuilder.searchRequest).subscribe(results => { this.setData(results); + this.searchView(false); }); } } @@ -384,7 +320,6 @@ export class AlertsListComponent implements OnInit, OnDestroy { updateConfigRowsSettings() { this.searchService.interval = this.refreshInterval; - this.queryBuilder.setFromAndSize(this.pagingData.from, this.pagingData.size); } updateSelectedAlertStatus(status: string) { http://git-wip-us.apache.org/repos/asf/metron/blob/529ea6cc/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts index e6adae3..c1025f0 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts @@ -26,13 +26,14 @@ import {ListGroupModule} from '../../shared/list-group/list-grup.module'; import {CollapseModule} from '../../shared/collapse/collapse.module'; import {MetronTablePaginationModule} from '../../shared/metron-table/metron-table-pagination/metron-table-pagination.module'; import {ConfigureRowsModule} from '../configure-rows/configure-rows.module'; +import {TableViewComponent} from './table-view/table-view.component'; @NgModule({ imports: [routing, SharedModule, ConfigureRowsModule, MetronSorterModule, MetronTablePaginationModule, ListGroupModule, CollapseModule], exports: [AlertsListComponent], - declarations: [AlertsListComponent], - providers: [SearchService], + declarations: [AlertsListComponent, TableViewComponent], + providers: [SearchService] }) export class AlertsListModule { } http://git-wip-us.apache.org/repos/asf/metron/blob/529ea6cc/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts index 9816f46..0b76ee1 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts @@ -62,12 +62,12 @@ export class QueryBuilder { this.query = this._searchRequest.query; } - addOrUpdateFilter(field: string, value: string) { - let filter = this._filters.find(tFilter => tFilter.field === field); - if (filter) { - filter.value = value; + addOrUpdateFilter(filter: Filter) { + let existingFilter = this._filters.find(tFilter => tFilter.field === filter.field); + if (existingFilter) { + existingFilter.value = filter.value; } else { - this._filters.push(new Filter(field, value)); + this._filters.push(filter); } this.onSearchChange(); @@ -130,7 +130,7 @@ export class QueryBuilder { let field = term.substring(0, separatorPos).replace('\\', ''); field = updateNameTransform ? ColumnNamesService.getColumnDisplayKey(field) : field; let value = term.substring(separatorPos + 1, term.length); - this.addOrUpdateFilter(field, value); + this.addOrUpdateFilter(new Filter(field, value)); } } } http://git-wip-us.apache.org/repos/asf/metron/blob/529ea6cc/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.html b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.html new file mode 100644 index 0000000..8fd0077 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.html @@ -0,0 +1,42 @@ +<!-- + Licensed to the Apache Software + Foundation (ASF) under one or more contributor license agreements. See the + NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to You under the Apache License, + Version 2.0 (the "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed + under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + OR CONDITIONS OF ANY KIND, either express or implied. See the License for + the specific language governing permissions and limitations under the License. + --> +<div class="table-wrapper"> + <table class="table table-sm" metron-config-table [data]="alerts" [cellSelectable]="true" (onSort)="onSort($event)" style="white-space: nowrap;" (window:resize)="resize()" #table> + <thead> + <tr> + <th style="width:55px"> <metron-config-sorter [type]="'number'" [sortBy]="threatScoreFieldName"> Score </metron-config-sorter> </th> + <th *ngFor="let column of alertsColumnsToDisplay" [id]="column.name"> <metron-config-sorter [type]="column.type" [sortBy]="column.name" title="{{column.name}}"> {{ column.name | columnNameTranslate | centerEllipses:15 }}</metron-config-sorter> </th> + <th style="width:25px"><i class="fa fa-cog configure-table-icon" (click)="showConfigureTable()"></i></th> + <th style="width:25px"><input id="select-deselect-all" class="fontawesome-checkbox" type="checkbox" (click)="selectAllRows($event)"><label for="select-deselect-all"></label></th> + </tr> + </thead> + <tbody> + <tr *ngFor="let alert of alerts" (click)="showDetails($event, alert)" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1}"> + <td (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])"> + <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]"> <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }} </a> </div> + </td> + <td *ngFor="let column of alertsColumnsToDisplay" #cell> + <a (click)="addFilter(column.name, getValue(alert, column, false))" title="{{getValue(alert, column, true)}}" style="color:#689AA9">{{ getValue(alert, column, true) | centerEllipses:20:cell }}</a> + </td> + <td></td> + <td><input id="{{ alert.id }}" class="fontawesome-checkbox" type="checkbox" name="{{alert.id}}" (click)="selectRow($event, alert)" [checked]="selectedAlerts.indexOf(alert) != -1"><label attr.for="{{ alert.id }}"></label></td> + </tr> + </tbody> + </table> +</div> +<div clas="row"> + <div class="col-md-3 push-md-5"> + <metron-table-pagination [(pagingData)]="pagingData" (pageChange)="onPageChange()"> </metron-table-pagination> + </div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metron/blob/529ea6cc/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss new file mode 100644 index 0000000..fa2417e --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../variables.scss"; + +.table-wrapper { + min-height: calc(100vh - 250px); +} + +.configure-table-icon { + font-size: 16px; + cursor: pointer; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metron/blob/529ea6cc/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts new file mode 100644 index 0000000..b4611e9 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableViewComponent } from './table-view.component'; + +describe('TableViewComponent', () => { + let component: TableViewComponent; + let fixture: ComponentFixture<TableViewComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TableViewComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TableViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); http://git-wip-us.apache.org/repos/asf/metron/blob/529ea6cc/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.ts new file mode 100644 index 0000000..49f969f --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.ts @@ -0,0 +1,172 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import {Router} from '@angular/router'; + +import {Pagination} from '../../../model/pagination'; +import {SortEvent} from '../../../shared/metron-table/metron-table.directive'; +import {ColumnMetadata} from '../../../model/column-metadata'; +import {Alert} from '../../../model/alert'; +import {SearchResponse} from '../../../model/search-response'; +import {SearchService} from '../../../service/search.service'; +import {MetronDialogBox, DialogType} from '../../../shared/metron-dialog-box'; +import {ElasticsearchUtils} from '../../../utils/elasticsearch-utils'; +import {QueryBuilder} from '../query-builder'; +import {Sort} from '../../../utils/enums'; +import {Filter} from '../../../model/filter'; + +@Component({ + selector: 'app-table-view', + templateUrl: './table-view.component.html', + styleUrls: ['./table-view.component.scss'] +}) + +export class TableViewComponent { + + alerts: Alert[] = []; + threatScoreFieldName = 'threat:triage:score'; + + router: Router; + searchService: SearchService; + metronDialogBox: MetronDialogBox; + pagingData = new Pagination(); + searchResponse: SearchResponse = new SearchResponse(); + + @Input() queryBuilder: QueryBuilder; + @Input() alertsColumnsToDisplay: ColumnMetadata[] = []; + @Input() selectedAlerts: Alert[] = []; + + @Output() onResize = new EventEmitter<void>(); + @Output() onAddFilter = new EventEmitter<Filter>(); + @Output() onShowDetails = new EventEmitter<Alert>(); + @Output() onShowConfigureTable = new EventEmitter<Alert>(); + @Output() selectedAlertsChange = new EventEmitter< Alert[]>(); + + constructor(router: Router, + searchService: SearchService, + metronDialogBox: MetronDialogBox) { + this.router = router; + this.searchService = searchService; + this.metronDialogBox = metronDialogBox; + } + + search(resetPaginationParams = true, pageSize: number = null) { + if (resetPaginationParams) { + this.pagingData.from = 0; + } + + this.pagingData.size = pageSize === null ? this.pagingData.size : pageSize; + this.queryBuilder.setFromAndSize(this.pagingData.from, this.pagingData.size); + + this.searchService.search(this.queryBuilder.searchRequest).subscribe(results => { + this.setAlertData(results); + }, error => { + this.setAlertData(new SearchResponse()); + this.metronDialogBox.showConfirmationMessage(ElasticsearchUtils.extractESErrorMessage(error), DialogType.Error); + }); + } + + setAlertData(results: SearchResponse) { + this.searchResponse = results; + this.pagingData.total = results.total; + this.alerts = this.searchResponse.results ? this.searchResponse.results : []; + } + + onSort(sortEvent: SortEvent) { + let sortOrder = (sortEvent.sortOrder === Sort.ASC ? 'asc' : 'desc'); + let sortBy = sortEvent.sortBy === 'id' ? '_uid' : sortEvent.sortBy; + this.queryBuilder.setSort(sortBy, sortOrder); + this.search(); + } + + getValue(alert: Alert, column: ColumnMetadata, formatData: boolean) { + let returnValue = ''; + try { + switch (column.name) { + case 'id': + returnValue = alert[column.name]; + break; + case 'alert_status': + returnValue = 'NEW'; + break; + default: + returnValue = alert.source[column.name]; + break; + } + } catch (e) {} + + if (formatData) { + returnValue = this.formatValue(column, returnValue); + } + + return returnValue; + } + + formatValue(column: ColumnMetadata, returnValue: string) { + try { + if (column.name.endsWith(':ts') || column.name.endsWith('timestamp')) { + returnValue = new Date(parseInt(returnValue, 10)).toISOString().replace('T', ' ').slice(0, 19); + } + } catch (e) {} + + return returnValue; + } + + onPageChange() { + this.search(false); + } + + selectRow($event, alert: Alert) { + if ($event.target.checked) { + this.selectedAlerts.push(alert); + } else { + this.selectedAlerts.splice(this.selectedAlerts.indexOf(alert), 1); + } + + this.selectedAlertsChange.emit(this.selectedAlerts); + } + + selectAllRows($event) { + this.selectedAlerts = []; + if ($event.target.checked) { + this.selectedAlerts = this.alerts; + } + + this.selectedAlertsChange.emit(this.selectedAlerts); + } + + resize() { + this.onResize.emit(); + } + + addFilter(field: string, value: string) { + field = (field === 'id') ? '_uid' : field; + this.onAddFilter.emit(new Filter(field, value)); + } + + showDetails($event, alert: Alert) { + if ($event.target.parentElement.firstElementChild.type !== 'checkbox' && $event.target.nodeName !== 'A') { + this.onShowDetails.emit(alert); + } + } + + showConfigureTable() { + this.onShowConfigureTable.emit(); + } +}
