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();
+  }
+}