You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by rm...@apache.org on 2017/09/21 17:49:57 UTC
metron git commit: METRON-1182 Refactor Code in alert list to
accommodate new view types (iraghumitra via merrimanr) closes
apache/metron#756
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 <ra...@gmail.com>
Authored: Thu Sep 21 12:49:28 2017 -0500
Committer: merrimanr <me...@apache.org>
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();
+ }
+}