Repository: metron Updated Branches: refs/heads/master a1408d787 -> d07833a25
http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 index fa2f97e..91489b8 100644 --- 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 @@ -16,7 +16,7 @@ * limitations under the License. */ -import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; import {Router} from '@angular/router'; import {Pagination} from '../../../model/pagination'; @@ -28,6 +28,18 @@ import {MetronDialogBox} from '../../../shared/metron-dialog-box'; import {QueryBuilder} from '../query-builder'; import {Sort} from '../../../utils/enums'; import {Filter} from '../../../model/filter'; +import {AlertSource} from '../../../model/alert-source'; +import {PatchRequest} from '../../../model/patch-request'; +import {Patch} from '../../../model/patch'; +import {UpdateService} from '../../../service/update.service'; +import {META_ALERTS_INDEX} from '../../../utils/constants'; +import {MetaAlertService} from '../../../service/meta-alert.service'; +import {MetaAlertAddRemoveRequest} from '../../../model/meta-alert-add-remove-request'; +import {GetRequest} from '../../../model/get-request'; + +export enum MetronAlertDisplayState { + COLLAPSE, EXPAND +} @Component({ selector: 'app-table-view', @@ -35,21 +47,25 @@ import {Filter} from '../../../model/filter'; styleUrls: ['./table-view.component.scss'] }) -export class TableViewComponent { +export class TableViewComponent implements OnChanges { - threatScoreFieldName = 'threat:triage:score'; router: Router; searchService: SearchService; + updateService: UpdateService; + isStatusFieldPresent = false; metronDialogBox: MetronDialogBox; + metaAlertService: MetaAlertService; + metaAlertsDisplayState: {[key: string]: MetronAlertDisplayState} = {}; + metronAlertDisplayState = MetronAlertDisplayState; @Input() alerts: Alert[] = []; @Input() queryBuilder: QueryBuilder; @Input() pagination: Pagination; @Input() alertsColumnsToDisplay: ColumnMetadata[] = []; @Input() selectedAlerts: Alert[] = []; - + @Output() onResize = new EventEmitter<void>(); @Output() onAddFilter = new EventEmitter<Filter>(); @Output() onRefreshData = new EventEmitter<boolean>(); @@ -59,43 +75,89 @@ export class TableViewComponent { constructor(router: Router, searchService: SearchService, - metronDialogBox: MetronDialogBox) { + metronDialogBox: MetronDialogBox, + updateService: UpdateService, + metaAlertService: MetaAlertService) { this.router = router; this.searchService = searchService; this.metronDialogBox = metronDialogBox; + this.updateService = updateService; + this.metaAlertService = metaAlertService; + } + + ngOnChanges(changes: SimpleChanges) { + if (changes && changes['alerts'] && changes['alerts'].currentValue) { + let expandedMetaAlerts = this.getGUIDOfAllExpandedMetaAlerts(); + this.updateExpandedStateForChangedData(expandedMetaAlerts); + } + + if (changes && changes['alertsColumnsToDisplay'] && changes['alertsColumnsToDisplay'].currentValue) { + this.isStatusFieldPresent = this.alertsColumnsToDisplay.some(col => col.name === 'alert_status'); + } + } + + updateExpandedStateForChangedData(expandedMetaAlerts: string[]) { + this.alerts.forEach(alert => { + if (alert.source.alert && alert.source.alert.length > 0) { + this.metaAlertsDisplayState[alert.id] = expandedMetaAlerts.indexOf(alert.id) === -1 ? + MetronAlertDisplayState.COLLAPSE : MetronAlertDisplayState.EXPAND; + } + }); + } + + getGUIDOfAllExpandedMetaAlerts(): string[] { + let expandedMetaAlerts = []; + Object.keys(this.metaAlertsDisplayState).forEach(id => { + if (this.metaAlertsDisplayState[id] === MetronAlertDisplayState.EXPAND) { + expandedMetaAlerts.push(id); + } + }); + + return expandedMetaAlerts; } onSort(sortEvent: SortEvent) { let sortOrder = (sortEvent.sortOrder === Sort.ASC ? 'asc' : 'desc'); - let sortBy = sortEvent.sortBy === 'id' ? '_uid' : sortEvent.sortBy; + let sortBy = sortEvent.sortBy === 'id' ? 'guid' : sortEvent.sortBy; this.queryBuilder.setSort(sortBy, sortOrder); this.onRefreshData.emit(true); } getValue(alert: Alert, column: ColumnMetadata, formatData: boolean) { + if (column.name === 'id') { + return this.formatValue(column, alert[column.name]); + } + + return this.getValueFromSource(alert.source, column, formatData); + } + + getValueFromSource(alertSource: AlertSource, column: ColumnMetadata, formatData: boolean) { let returnValue = ''; try { switch (column.name) { case 'id': - returnValue = alert[column.name]; + returnValue = alertSource['guid']; break; case 'alert_status': - let alertStatus = alert.source['alert_status']; - returnValue = alertStatus ? alertStatus : 'NEW'; + returnValue = alertSource['alert_status'] ? alertSource['alert_status'] : 'NEW'; break; default: - returnValue = alert.source[column.name]; + returnValue = alertSource[column.name]; break; } - } catch (e) {} + } catch (e) { + } if (formatData) { returnValue = this.formatValue(column, returnValue); } - return returnValue; } + fireSelectedAlertsChanged() { + this.onSelectedAlertsChange.emit(this.selectedAlerts); + } + formatValue(column: ColumnMetadata, returnValue: string) { try { if (column.name.endsWith(':ts') || column.name.endsWith('timestamp')) { @@ -117,8 +179,7 @@ export class TableViewComponent { } else { this.selectedAlerts.splice(this.selectedAlerts.indexOf(alert), 1); } - - this.onSelectedAlertsChange.emit(this.selectedAlerts); + this.fireSelectedAlertsChanged(); } selectAllRows($event) { @@ -126,8 +187,7 @@ export class TableViewComponent { if ($event.target.checked) { this.selectedAlerts = this.alerts; } - - this.onSelectedAlertsChange.emit(this.selectedAlerts); + this.fireSelectedAlertsChanged(); } resize() { @@ -135,10 +195,17 @@ export class TableViewComponent { } addFilter(field: string, value: string) { - field = (field === 'id') ? '_uid' : field; + field = (field === 'id') ? 'guid' : field; this.onAddFilter.emit(new Filter(field, value)); } + showMetaAlertDetails($event, alertSource: AlertSource) { + let alert = new Alert(); + alert.source = alertSource; + alert.index = META_ALERTS_INDEX; + this.showDetails($event, alert); + } + showDetails($event, alert: Alert) { if ($event.target.parentElement.firstElementChild.type !== 'checkbox' && $event.target.nodeName !== 'A') { this.onShowDetails.emit(alert); @@ -148,4 +215,48 @@ export class TableViewComponent { showConfigureTable() { this.onShowConfigureTable.emit(); } + + toggleExpandCollapse($event, alert: Alert) { + if (this.metaAlertsDisplayState[alert.id] === MetronAlertDisplayState.COLLAPSE) { + this.metaAlertsDisplayState[alert.id] = MetronAlertDisplayState.EXPAND; + } else { + this.metaAlertsDisplayState[alert.id] = MetronAlertDisplayState.COLLAPSE; + } + + $event.stopPropagation(); + return false; + } + + deleteOneAlertFromMetaAlert($event, alert: Alert, metaAlertIndex: number) { + this.metronDialogBox.showConfirmationMessage('Do you wish to remove the alert from the meta alert?').subscribe(response => { + if (response) { + this.doDeleteOneAlertFromMetaAlert(alert, metaAlertIndex); + } + }); + $event.stopPropagation(); + } + + deleteMetaAlert($event, alert: Alert, index: number) { + this.metronDialogBox.showConfirmationMessage('Do you wish to remove all the alerts from meta alert?').subscribe(response => { + if (response) { + this.doDeleteMetaAlert(alert, index); + } + }); + $event.stopPropagation(); + } + + doDeleteOneAlertFromMetaAlert(alert, metaAlertIndex) { + let alertToRemove = alert.source.alert[metaAlertIndex]; + let metaAlertAddRemoveRequest = new MetaAlertAddRemoveRequest(); + metaAlertAddRemoveRequest.metaAlertGuid = alert.source.guid; + metaAlertAddRemoveRequest.alerts = [new GetRequest(alertToRemove.guid, alertToRemove['source:type'], '')]; + + this.metaAlertService.removeAlertsFromMetaAlert(metaAlertAddRemoveRequest).subscribe(() => { + }); + } + + doDeleteMetaAlert(alert: Alert, index: number) { + this.metaAlertService.updateMetaAlertStatus(alert.source.guid, 'inactive').subscribe(() => { + }); + } } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts index ae65a67..ae5cdd3 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts @@ -30,6 +30,7 @@ export class TreeGroupData { show: boolean; expand = false; score: number; + isLeafNode = false; // Used by only Dashrow sortField: SortField; http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html index e89cdc9..582117e 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html @@ -20,7 +20,10 @@ </div> <div class="mrow top-group" (click)="toggleTopLevelGroup(group)"> <div class="col-5 text-light severity-padding"> <span class="title"> {{ group.key | centerEllipses:45 }} </span> </div> - <div class="col-6 text-light two-line"> <span class="text-dark"> ALERTS </span> <br> <span class="title"> {{ group.total | number }} </span> </div> + <div class="col-5 text-light two-line"> <span class="text-dark"> ALERTS </span> <br> <span class="title"> {{ group.total | number }} </span> </div> + <div class="col-1 text-right pr-4"> + <i class="fa fa-link top-group-link" aria-hidden="true" (click)="createMetaAlert($event, group, i)" data-animation="false" data-toggle="tooltip" data-placement="left" title="Merge Alerts"></i> + </div> <div class="col-1 text-right pr-4"> <i class="down-arrow" data-animation="false" data-toggle="tooltip" data-placement="left" title="Open Group" aria-expanded="false" [attr.href]="'#body-' + i" [attr.aria-controls]="'body-' + i"> </i> @@ -73,13 +76,17 @@ <ng-container *ngFor="let subGroup of group.treeSubGroups;let i = index"> <tr class="table-group-row" [ngClass]="{'d-none': !subGroup.show}" (click)="toggleSubGroups(group, subGroup, i)" [attr.data-name]="subGroup.key"> - <td [attr.colspan]="alertsColumnsToDisplay.length + 4" [ngStyle]="{'padding-left.px': (16 * (subGroup.level -1))}"> + <td [attr.colspan]="alertsColumnsToDisplay.length + 2" [ngStyle]="{'padding-left.px': (16 * (subGroup.level -1))}"> <span class="table-group-icon-col" data-animation="false" data-toggle="tooltip" data-placement="bottom" title="Open Group"> <i class="fa" aria-hidden="true" [ngClass]="{'fa-caret-down': subGroup.expand, 'fa-caret-right': !subGroup.expand}"></i> </span> <span class="score" appAlertSeverity [severity]="subGroup.score"> {{ subGroup.score }} </span> <span class="group-value"> <span class="text-light"> {{ subGroup.key }} </span> ({{ subGroup.total}})</span> </td> + <td> + <i style="color: #32ABDF;" class="fa fa-link" aria-hidden="true" (click)="createMetaAlert($event, subGroup, i)" data-animation="false" data-toggle="tooltip" data-placement="left" title="Merge Alerts"></i> + </td> + <td> </td> </tr> <tr *ngFor="let alert of subGroup.response.results" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1, 'd-none': !subGroup.expand || !subGroup.show}" (click)="showDetails($event, alert)"> http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss index 8668b49..084ba5d 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss @@ -55,6 +55,17 @@ $group-height: 70px; background: $mine-shaft-8; } + .fa-link, .fa-chain-broken { + + color: $piction-blue; + border: 2px solid transparent; + + &:hover { + background: $eden-1; + border: 2px solid $blue-mine; + } + } + .down-arrow { padding: 10px 20px; @@ -150,4 +161,10 @@ sup { .top-group { cursor: pointer; +} + +.top-group-link { + padding: 10px; + font-size: 22px; + margin-top: 12px; } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts index 75a7e1c..bb2b174 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts @@ -16,9 +16,9 @@ * limitations under the License. */ -import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, OnInit, OnChanges, SimpleChanges, OnDestroy } from '@angular/core'; import {Router} from '@angular/router'; -import {Subscription} from 'rxjs/Rx'; +import {Subscription, Observable} from 'rxjs/Rx'; import {TableViewComponent} from '../table-view/table-view.component'; import {SearchResponse} from '../../../model/search-response'; @@ -26,14 +26,17 @@ import {SearchService} from '../../../service/search.service'; import {TreeGroupData, TreeAlertsSubscription} from './tree-group-data'; import {GroupResponse} from '../../../model/group-response'; import {GroupResult} from '../../../model/group-result'; -import {Group} from '../../../model/group'; import {SortField} from '../../../model/sort-field'; import {Sort} from '../../../utils/enums'; import {MetronDialogBox, DialogType} from '../../../shared/metron-dialog-box'; import {ElasticsearchUtils} from '../../../utils/elasticsearch-utils'; import {SearchRequest} from '../../../model/search-request'; +import {MetaAlertCreateRequest} from '../../../model/meta-alert-create-request'; +import {MetaAlertService} from '../../../service/meta-alert.service'; +import {INDEXES, MAX_ALERTS_IN_META_ALERTS} from '../../../utils/constants'; import {UpdateService} from '../../../service/update.service'; import {PatchRequest} from '../../../model/patch-request'; +import {GetRequest} from '../../../model/get-request'; @Component({ selector: 'app-tree-view', @@ -41,26 +44,32 @@ import {PatchRequest} from '../../../model/patch-request'; styleUrls: ['./tree-view.component.scss'] }) -export class TreeViewComponent extends TableViewComponent implements OnChanges { +export class TreeViewComponent extends TableViewComponent implements OnInit, OnChanges, OnDestroy { groupByFields: string[] = []; topGroups: TreeGroupData[] = []; groupResponse: GroupResponse = new GroupResponse(); treeGroupSubscriptionMap: {[key: string]: TreeAlertsSubscription } = {}; + alertsChangedSubscription: Subscription; constructor(router: Router, searchService: SearchService, metronDialogBox: MetronDialogBox, - private updateService: UpdateService) { - super(router, searchService, metronDialogBox); + updateService: UpdateService, + metaAlertService: MetaAlertService) { + super(router, searchService, metronDialogBox, updateService, metaAlertService); } addAlertChangedListner() { - this.updateService.alertChanged$.subscribe(patchRequest => { + this.alertsChangedSubscription = this.updateService.alertChanged$.subscribe(patchRequest => { this.updateAlert(patchRequest); }); } + removeAlertChangedLister() { + this.alertsChangedSubscription.unsubscribe(); + } + collapseGroup(groupArray: TreeGroupData[], level: number, index: number) { for (let i = index + 1; i < groupArray.length; i++) { if (groupArray[i].level > (level)) { @@ -132,6 +141,8 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { this.groupResponse.groupResults.forEach((groupResult: GroupResult) => { let treeGroupData = new TreeGroupData(groupResult.key, groupResult.total, groupResult.score, 0, false); + treeGroupData.isLeafNode = (groupByFields.length === 1); + if (groupByFields.length === 1) { treeGroupData.groupQueryMap = this.createTopGroupQueryMap(groupByFields[0], groupResult); } @@ -173,6 +184,10 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { this.addAlertChangedListner(); } + ngOnDestroy(): void { + this.removeAlertChangedLister(); + } + searchGroup(selectedGroup: TreeGroupData, searchRequest: SearchRequest): Subscription { return this.searchService.search(searchRequest).subscribe(results => { this.setData(selectedGroup, results); @@ -188,13 +203,13 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { this.topGroups.map(topGroup => { if (topGroup.treeSubGroups.length > 0) { - topGroup.total = topGroup.treeSubGroups.reduce((total, subGroup) => { return total + subGroup.total }, 0); + topGroup.total = topGroup.treeSubGroups.reduce((total, subGroup) => { return total + subGroup.total; }, 0); } }); } checkAndToSubscription(group: TreeGroupData) { - if (group.groupQueryMap) { + if (group.isLeafNode) { let key = JSON.stringify(group.groupQueryMap); if (this.treeGroupSubscriptionMap[key]) { this.removeFromSubscription(group); @@ -206,7 +221,7 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { } removeFromSubscription(group: TreeGroupData) { - if (group.groupQueryMap) { + if (group.isLeafNode) { let key = JSON.stringify(group.groupQueryMap); let subscription = this.treeGroupSubscriptionMap[key].refreshTimer; if (subscription && !subscription.closed) { @@ -240,9 +255,8 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { } parseSubGroups(group: GroupResult, groupAsArray: TreeGroupData[], - groupQueryMap: {[key: string]: string}, groupedBy: string, level: number, index: number): number { + parentQueryMap: {[key: string]: string}, currentGroupKey: string, level: number, index: number): number { index++; - groupQueryMap[groupedBy] = group.key; let currentTreeNodeData = (groupAsArray.length > 0) ? groupAsArray[index] : null; @@ -257,8 +271,12 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { } } + groupAsArray[index].isLeafNode = false; + groupAsArray[index].groupQueryMap = JSON.parse(JSON.stringify(parentQueryMap)); + groupAsArray[index].groupQueryMap[currentGroupKey] = group.key; + if (!group.groupResults) { - groupAsArray[index].groupQueryMap = JSON.parse(JSON.stringify(groupQueryMap)); + groupAsArray[index].isLeafNode = true; if (groupAsArray[index].expand && groupAsArray[index].show && groupAsArray[index].groupQueryMap) { this.checkAndToSubscription(groupAsArray[index]); } @@ -266,7 +284,7 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { } group.groupResults.forEach(subGroup => { - index = this.parseSubGroups(subGroup, groupAsArray, groupQueryMap, group.groupedBy, level + 1, index); + index = this.parseSubGroups(subGroup, groupAsArray, groupAsArray[index].groupQueryMap, group.groupedBy, level + 1, index); }); return index; @@ -281,13 +299,13 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { let index = -1; let topGroup = this.topGroups[i]; let resultGroup = this.groupResponse.groupResults[i]; - let groupQueryMap = this.createTopGroupQueryMap(groupedBy, resultGroup); topGroup.total = resultGroup.total; + topGroup.groupQueryMap = this.createTopGroupQueryMap(groupedBy, resultGroup); if (resultGroup.groupResults) { resultGroup.groupResults.forEach(subGroup => { - index = this.parseSubGroups(subGroup, topGroup.treeSubGroups, groupQueryMap, resultGroup.groupedBy, 1, index); + index = this.parseSubGroups(subGroup, topGroup.treeSubGroups, topGroup.groupQueryMap, resultGroup.groupedBy, 1, index); }); topGroup.treeSubGroups.splice(index + 1); @@ -300,11 +318,9 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { } sortTreeSubGroup($event, treeGroup: TreeGroupData) { - let sortBy = $event.sortBy === 'id' ? '_uid' : $event.sortBy; - - let sortField = new SortField(); - sortField.field = sortBy; - sortField.sortOrder = $event.sortOrder === Sort.ASC ? 'asc' : 'desc'; + let sortBy = $event.sortBy === 'id' ? 'guid' : $event.sortBy; + let sortOrder = $event.sortOrder === Sort.ASC ? 'asc' : 'desc'; + let sortField = new SortField(sortBy, sortOrder); treeGroup.sortEvent = $event; treeGroup.sortField = sortField; @@ -327,7 +343,7 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { } }); } - + this.onSelectedAlertsChange.emit(this.selectedAlerts); } @@ -337,12 +353,67 @@ export class TreeViewComponent extends TableViewComponent implements OnChanges { }); } + canCreateMetaAlert(count: number) { + if (count > MAX_ALERTS_IN_META_ALERTS) { + let errorMessage = 'Meta Alert cannot have more than ' + MAX_ALERTS_IN_META_ALERTS +' alerts within it'; + this.metronDialogBox.showConfirmationMessage(errorMessage, DialogType.Error).subscribe((response) => {}); + return false; + } + return true; + } + + createGetRequestArray(searchResponse: SearchResponse): any { + return searchResponse.results.map(alert => new GetRequest(alert.source.guid, alert.source['source:type'], alert.index)); + } + + getAllAlertsForSlectedGroup(group: TreeGroupData): Observable<SearchResponse> { + let dashRowKey = Object.keys(group.groupQueryMap); + let searchRequest = new SearchRequest(); + searchRequest.fields = ['guid', 'source:type']; + searchRequest.from = 0; + searchRequest.indices = INDEXES; + searchRequest.query = this.createQuery(group); + searchRequest.size = MAX_ALERTS_IN_META_ALERTS; + searchRequest.facetFields = []; + return this.searchService.search(searchRequest); + } + + doCreateMetaAlert(group: TreeGroupData, index: number) { + this.getAllAlertsForSlectedGroup(group).subscribe((searchResponse: SearchResponse) => { + if (this.canCreateMetaAlert(searchResponse.total)) { + let metaAlert = new MetaAlertCreateRequest(); + metaAlert.alerts = this.createGetRequestArray(searchResponse); + metaAlert.groups = this.queryBuilder.groupRequest.groups.map(grp => grp.field); + + this.metaAlertService.create(metaAlert).subscribe(() => { + setTimeout(() => this.onRefreshData.emit(true), 1000); + console.log('Meta alert created successfully'); + }); + } + }); + } + + createMetaAlert($event, group: TreeGroupData, index: number) { + if (this.canCreateMetaAlert(group.total)) { + let confirmationMsg = 'Do you wish to create a meta alert with ' + + (group.total === 1 ? ' alert' : group.total + ' selected alerts') + '?'; + this.metronDialogBox.showConfirmationMessage(confirmationMsg).subscribe((response) => { + if (response) { + this.doCreateMetaAlert(group, index); + } + }); + } + + $event.stopPropagation(); + return false; + } + updateAlert(patchRequest: PatchRequest) { this.searchService.getAlert(patchRequest.sensorType, patchRequest.guid).subscribe(alertSource => { Object.keys(this.treeGroupSubscriptionMap).forEach(key => { let group = this.treeGroupSubscriptionMap[key].group; - if(group.response && group.response.results && group.response.results.length > 0) { + if (group.response && group.response.results && group.response.results.length > 0) { group.response.results.filter(alert => alert.source.guid === patchRequest.guid) .map(alert => alert.source = alertSource); } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.html b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.html new file mode 100644 index 0000000..46b1d7d --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.html @@ -0,0 +1,52 @@ +<!-- + 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="metron-slider-pane-details load-right-to-left dialog1x"> + <div class="container-fluid my-3"> + + <div class="row mb-3"> + <div class="col-md-10"> + <div class="form-title">Add to Alert</div> + </div> + <div class="col-md-2"> + <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="goBack()"></i> + </div> + </div> + + <div> + <div class="title"> SELECT OPEN ALERT</div> + <div class="container-fluid"> + <div class="row meta-alert-row" *ngFor="let alert of searchResponse.results"> + <div class="col-1 px-0"> + <label class="radio-container"> + <input type="radio" name="radio"><span class="checkmark" (click)="selectedMetaAlert=alert.source.guid"></span> + </label> + </div> + <div class="col-11 px-0"> + <span class="severity" appAlertSeverity [severity]="alert.source['threat:triage:score']"></span><sup> {{ alert.source['threat:triage:score'] }} </sup> + <div class="px-0 guid-name-container"> + <div [ngClass]="{'selected': selectedMetaAlert===alert.source.guid}"> {{(alert.source.name && alert.source.name.length > 0) ? alert.source.name : alert.source.guid | centerEllipses:20 }} ({{ alert.source.alert.length }})</div> + <span class="pull-left sub-text"> {{ (alert.source.alert_status && alert.source.alert_status.length > 0) ? alert.source.alert_status : 'NEW' }} </span> + <span class="pull-right sub-text"> {{ alert.source.timestamp | timeLapse }} </span> + </div> + </div> + </div> + + <div class="my-4"> + <button type="submit" class="btn btn-all_ports" (click)="addAlertToMetaAlert()" [disabled]="selectedMetaAlert.length===0" title="Please select a meta alert">ADD</button> + <button class="btn btn-mine_shaft_2" (click)="goBack()">CANCEL</button> + </div> + </div> + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.scss b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.scss new file mode 100644 index 0000000..6a0f3a9 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.scss @@ -0,0 +1,58 @@ +/** + * 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"; + +input { + height: 13px; +} + +.radio-container { + margin-top: 12px; +} + +.title { + font-size: 12px; + padding-bottom: 2px; + border-bottom: 1px solid $tundora-1; +} + +.meta-alert-row { + padding: 4px 0px; + border-bottom: 1px solid $tundora-1; + + .severity { + height: 20px; + } + + .guid-name-container { + width: 84%; + font-size: 15px; + line-height: 1.4; + margin-left: 5px; + display: inline-block; + } + + .sub-text { + font-size: 10px; + } + + sup, .selected { + color: $silver; + font-weight: bold; + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.ts b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.ts new file mode 100644 index 0000000..18a20da --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.component.ts @@ -0,0 +1,79 @@ +/** + * 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, OnInit } from '@angular/core'; +import {Router} from '@angular/router'; + +import {MetaAlertService} from '../../service/meta-alert.service'; +import {UpdateService} from '../../service/update.service'; +import {SearchRequest} from '../../model/search-request'; +import {SearchService} from '../../service/search.service'; +import {SearchResponse} from '../../model/search-response'; +import {SortField} from '../../model/sort-field'; +import { META_ALERTS_SENSOR_TYPE } from '../../utils/constants'; +import {MetronDialogBox} from '../../shared/metron-dialog-box'; +import {MetaAlertAddRemoveRequest} from '../../model/meta-alert-add-remove-request'; +import {GetRequest} from '../../model/get-request'; + +@Component({ + selector: 'app-meta-alerts', + templateUrl: './meta-alerts.component.html', + styleUrls: ['./meta-alerts.component.scss'] +}) +export class MetaAlertsComponent implements OnInit { + + selectedMetaAlert = ''; + searchResponse: SearchResponse = new SearchResponse(); + + constructor(private router: Router, + private metaAlertService: MetaAlertService, + private updateService: UpdateService, + private searchService: SearchService, + private metronDialogBox: MetronDialogBox) { + } + + goBack() { + this.router.navigateByUrl('/alerts-list'); + return false; + } + + ngOnInit() { + let searchRequest = new SearchRequest(); + searchRequest.query = '*'; + searchRequest.from = 0; + searchRequest.size = 999; + searchRequest.facetFields = []; + searchRequest.indices = [META_ALERTS_SENSOR_TYPE]; + searchRequest.sort = [new SortField('threat:triage:score', 'desc')]; + + this.searchService.search(searchRequest).subscribe(resp => this.searchResponse = resp); + } + + addAlertToMetaAlert() { + let getRequest = this.metaAlertService.selectedAlerts.map(alert => + new GetRequest(alert.source.guid, alert.source['source:type'], alert.index)); + let metaAlertAddRemoveRequest = new MetaAlertAddRemoveRequest(); + metaAlertAddRemoveRequest.metaAlertGuid = this.selectedMetaAlert; + metaAlertAddRemoveRequest.alerts = getRequest; + + this.metaAlertService.addAlertsToMetaAlert(metaAlertAddRemoveRequest).subscribe(() => { + console.log('Meta alert saved'); + this.goBack(); + }); + } + +} http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.module.ts b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.module.ts new file mode 100644 index 0000000..578063d --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; + +import {routing} from './meta-alerts.routing'; +import { MetaAlertsComponent } from './meta-alerts.component'; +import {MetaAlertService} from '../../service/meta-alert.service'; +import {SharedModule} from '../../shared/shared.module'; + +@NgModule({ + imports: [ routing, SharedModule ], + declarations: [ MetaAlertsComponent ], + providers: [ MetaAlertService ], +}) +export class MetaAlertsModule { } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.routing.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.routing.ts b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.routing.ts new file mode 100644 index 0000000..f4b2413 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/meta-alerts/meta-alerts.routing.ts @@ -0,0 +1,24 @@ +/** + * 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 { ModuleWithProviders } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { MetaAlertsComponent } from './meta-alerts.component'; + +export const routing: ModuleWithProviders = RouterModule.forChild([ + { path: 'add-to-meta-alert', component: MetaAlertsComponent, outlet: 'dialog'} +]); http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/app.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/app.module.ts b/metron-interface/metron-alerts/src/app/app.module.ts index c817831..bd34e82 100644 --- a/metron-interface/metron-alerts/src/app/app.module.ts +++ b/metron-interface/metron-alerts/src/app/app.module.ts @@ -42,6 +42,9 @@ import {AuthGuard} from './shared/auth-guard'; import {AuthenticationService} from './service/authentication.service'; import {LoginGuard} from './shared/login-guard'; import {UpdateService} from './service/update.service'; +import {MetaAlertService} from './service/meta-alert.service'; +import {MetaAlertsModule} from './alerts/meta-alerts/meta-alerts.module'; +import {SearchService} from './service/search.service'; @@ -61,6 +64,7 @@ export function initConfig(config: ColumnNamesService) { LoginModule, AlertsListModule, AlertDetailsModule, + MetaAlertsModule, ConfigureTableModule, ConfigureRowsModule, SaveSearchModule, @@ -73,10 +77,12 @@ export function initConfig(config: ColumnNamesService) { AuthGuard, LoginGuard, ConfigureTableService, + SearchService, SaveSearchService, MetronDialogBox, ColumnNamesService, - UpdateService], + UpdateService, + MetaAlertService], bootstrap: [AppComponent] }) http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/alert-source.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/alert-source.ts b/metron-interface/metron-alerts/src/app/model/alert-source.ts index 4e3a655..d5477dc 100644 --- a/metron-interface/metron-alerts/src/app/model/alert-source.ts +++ b/metron-interface/metron-alerts/src/app/model/alert-source.ts @@ -44,6 +44,7 @@ export class AlertSource { guid: string; sig_id: number; sig_generator: number; + alert: AlertSource[] = []; comments: AlertComment[] = []; 'threat:triage:score': number; 'threatinteljoinbolt:joiner:ts': number; http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/filter.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/filter.ts b/metron-interface/metron-alerts/src/app/model/filter.ts index 441add4..200e33c 100644 --- a/metron-interface/metron-alerts/src/app/model/filter.ts +++ b/metron-interface/metron-alerts/src/app/model/filter.ts @@ -15,9 +15,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {ElasticsearchUtils} from '../utils/elasticsearch-utils'; -import {TIMESTAMP_FIELD_NAME} from '../utils/constants'; import {Utils} from '../utils/utils'; +import {TIMESTAMP_FIELD_NAME} from '../utils/constants'; import {DateFilterValue} from './date-filter-value'; export class Filter { @@ -43,16 +42,33 @@ export class Filter { } getQueryString(): string { + if (this.field === 'guid') { + let valueWithQuote = '\"' + this.value + '\"'; + return this.createNestedQueryWithoutValueEscaping(this.field, valueWithQuote); + } + if (this.field === TIMESTAMP_FIELD_NAME && !this.display) { this.dateFilterValue = Utils.timeRangeToDateObj(this.value); if (this.dateFilterValue !== null && this.dateFilterValue.toDate !== null) { - return ElasticsearchUtils.escapeESField(this.field) + ':' + - '(>=' + this.dateFilterValue.fromDate + ' AND ' + ' <=' + this.dateFilterValue.toDate + ')'; + return this.createNestedQueryWithoutValueEscaping(this.field, + '(>=' + this.dateFilterValue.fromDate + ' AND ' + ' <=' + this.dateFilterValue.toDate + ')'); } else { - return ElasticsearchUtils.escapeESField(this.field) + ':' + this.value; + return this.createNestedQueryWithoutValueEscaping(this.field, this.value); } } - return ElasticsearchUtils.escapeESField(this.field) + ':' + ElasticsearchUtils.escapeESValue(this.value); + return this.createNestedQuery(this.field, this.value); + } + + private createNestedQuery(field: string, value: string): string { + + return '(' + Utils.escapeESField(field) + ':' + Utils.escapeESValue(value) + ' OR ' + + Utils.escapeESField('alert.' + field) + ':' + Utils.escapeESValue(value) + ')'; + } + + private createNestedQueryWithoutValueEscaping(field: string, value: string): string { + + return '(' + Utils.escapeESField(field) + ':' + value + ' OR ' + + Utils.escapeESField('alert.' + field) + ':' + value + ')'; } } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/get-request.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/get-request.ts b/metron-interface/metron-alerts/src/app/model/get-request.ts new file mode 100644 index 0000000..dcd3f12 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/model/get-request.ts @@ -0,0 +1,29 @@ +/** + * 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. + */ +export class GetRequest { + guid: string; + sensorType: string; + index: string; + + constructor(guid: string, sensorType: string, index: string) { + this.guid = guid; + this.sensorType = sensorType; + this.index = index; + } +} + http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/meta-alert-add-remove-request.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/meta-alert-add-remove-request.ts b/metron-interface/metron-alerts/src/app/model/meta-alert-add-remove-request.ts new file mode 100644 index 0000000..49cdbf8 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/model/meta-alert-add-remove-request.ts @@ -0,0 +1,23 @@ +/** + * 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 {GetRequest} from './get-request'; + +export class MetaAlertAddRemoveRequest { + metaAlertGuid: string; + alerts: GetRequest[]; +} http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/meta-alert-create-request.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/meta-alert-create-request.ts b/metron-interface/metron-alerts/src/app/model/meta-alert-create-request.ts new file mode 100644 index 0000000..0d7c63e --- /dev/null +++ b/metron-interface/metron-alerts/src/app/model/meta-alert-create-request.ts @@ -0,0 +1,24 @@ +/** + * 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 {GetRequest} from './get-request'; + +export class MetaAlertCreateRequest { + alerts: GetRequest[]; + groups: string[]; +} + http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/replace-request.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/replace-request.ts b/metron-interface/metron-alerts/src/app/model/replace-request.ts new file mode 100644 index 0000000..d7c18b2 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/model/replace-request.ts @@ -0,0 +1,24 @@ +/** + * 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. + */ + +export class ReplaceRequest { + replacement: {[key: string]: any}; + guid: string; + sensorType: string; + index: string; +} http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/search-request.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/search-request.ts b/metron-interface/metron-alerts/src/app/model/search-request.ts index d8dbcda..44e23db 100644 --- a/metron-interface/metron-alerts/src/app/model/search-request.ts +++ b/metron-interface/metron-alerts/src/app/model/search-request.ts @@ -19,7 +19,7 @@ import {SortField} from './sort-field'; import {DEFAULT_FACETS, DEFAULT_GROUPS, INDEXES} from '../utils/constants'; export class SearchRequest { - // _source: string[]; //TODO: This needs to be removed + fields: string[]; from: number; indices: string[] = INDEXES; query: string; http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/model/sort-field.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/sort-field.ts b/metron-interface/metron-alerts/src/app/model/sort-field.ts index 601b370..8fe7ce2 100644 --- a/metron-interface/metron-alerts/src/app/model/sort-field.ts +++ b/metron-interface/metron-alerts/src/app/model/sort-field.ts @@ -18,4 +18,9 @@ export class SortField { field: string; sortOrder: string; + + constructor(field: string, sortOrder: string) { + this.field = field; + this.sortOrder = sortOrder; + } } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/service/meta-alert.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/service/meta-alert.service.ts b/metron-interface/metron-alerts/src/app/service/meta-alert.service.ts new file mode 100644 index 0000000..c9bb26d --- /dev/null +++ b/metron-interface/metron-alerts/src/app/service/meta-alert.service.ts @@ -0,0 +1,88 @@ +/** + * 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 {Injectable} from '@angular/core'; +import {Headers, RequestOptions} from '@angular/http'; +import {Subject} from 'rxjs/Subject'; +import {Observable} from 'rxjs/Rx'; +import 'rxjs/add/observable/interval'; +import 'rxjs/add/operator/switchMap'; +import 'rxjs/add/operator/onErrorResumeNext'; + +import {HttpUtil} from '../utils/httpUtil'; +import {Alert} from '../model/alert'; +import {Http} from '@angular/http'; +import {MetaAlertCreateRequest} from '../model/meta-alert-create-request'; +import {MetaAlertAddRemoveRequest} from '../model/meta-alert-add-remove-request'; + +@Injectable() +export class MetaAlertService { + private _selectedAlerts: Alert[]; + alertChangedSource = new Subject<MetaAlertAddRemoveRequest>(); + alertChanged$ = this.alertChangedSource.asObservable(); + defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'}; + + constructor(private http: Http) { + } + + get selectedAlerts(): Alert[] { + return this._selectedAlerts; + } + + set selectedAlerts(value: Alert[]) { + this._selectedAlerts = value; + } + + public create(metaAlertCreateRequest: MetaAlertCreateRequest): Observable<{}> { + let url = '/api/v1/metaalert/create'; + return this.http.post(url, metaAlertCreateRequest, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .catch(HttpUtil.handleError); + } + + public addAlertsToMetaAlert(metaAlertAddRemoveRequest: MetaAlertAddRemoveRequest) { + let url = '/api/v1/metaalert/add/alert'; + return this.http.post(url, metaAlertAddRemoveRequest, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .catch(HttpUtil.handleError) + .map(result => { + this.alertChangedSource.next(metaAlertAddRemoveRequest); + return result; + }); + } + + public removeAlertsFromMetaAlert(metaAlertAddRemoveRequest: MetaAlertAddRemoveRequest) { + let url = '/api/v1/metaalert/remove/alert'; + return this.http.post(url, metaAlertAddRemoveRequest, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .catch(HttpUtil.handleError) + .map(result => { + this.alertChangedSource.next(metaAlertAddRemoveRequest); + return result; + }); + } + + public updateMetaAlertStatus(guid: string, status: string) { + let url = `/api/v1/metaalert/update/status/${guid}/${status}`; + return this.http.post(url, {}, new RequestOptions({headers: new Headers(this.defaultHeaders)})) + .catch(HttpUtil.handleError) + .map(result => { + let metaAlertAddRemoveRequest = new MetaAlertAddRemoveRequest(); + metaAlertAddRemoveRequest.metaAlertGuid = guid; + metaAlertAddRemoveRequest.alerts = null; + this.alertChangedSource.next(metaAlertAddRemoveRequest); + return result; + }); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/service/update.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/service/update.service.ts b/metron-interface/metron-alerts/src/app/service/update.service.ts index a1bf4a1..68d9fc0 100644 --- a/metron-interface/metron-alerts/src/app/service/update.service.ts +++ b/metron-interface/metron-alerts/src/app/service/update.service.ts @@ -27,6 +27,9 @@ import {HttpUtil} from '../utils/httpUtil'; import {Alert} from '../model/alert'; import {Http} from '@angular/http'; import {PatchRequest} from '../model/patch-request'; +import {Utils} from '../utils/utils'; +import {Patch} from '../model/patch'; +import {META_ALERTS_INDEX, META_ALERTS_SENSOR_TYPE} from '../utils/constants'; @Injectable() export class UpdateService { @@ -38,27 +41,32 @@ export class UpdateService { constructor(private http: Http) { } - public patch(patchRequest: PatchRequest): Observable<{}> { + public patch(patchRequest: PatchRequest, fireChangeListener = true): Observable<{}> { let url = '/api/v1/update/patch'; return this.http.patch(url, patchRequest, new RequestOptions({headers: new Headers(this.defaultHeaders)})) .catch(HttpUtil.handleError) .map(result => { - this.alertChangedSource.next(patchRequest); + if (fireChangeListener) { + this.alertChangedSource.next(patchRequest); + } return result; }); } - public updateAlertState(alerts: Alert[], state: string): Observable<{}> { + public updateAlertState(alerts: Alert[], state: string, fireChangeListener = true): Observable<{}> { let patchRequests: PatchRequest[] = alerts.map(alert => { let patchRequest = new PatchRequest(); patchRequest.guid = alert.source.guid; - patchRequest.sensorType = alert.source['source:type']; - patchRequest.patch = [{'op': 'add', 'path': '/alert_status', 'value': state}]; + patchRequest.sensorType = Utils.getAlertSensorType(alert); + patchRequest.patch = [new Patch('add', '/alert_status', state)]; + if (patchRequest.sensorType === META_ALERTS_SENSOR_TYPE) { + patchRequest.index = META_ALERTS_INDEX; + } return patchRequest; }); let patchObservables = []; for (let patchRequest of patchRequests) { - patchObservables.push(this.patch(patchRequest)); + patchObservables.push(this.patch(patchRequest, fireChangeListener)); } return Observable.forkJoin(patchObservables); } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts b/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts index 1b8e2bf..aaa2791 100644 --- a/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts +++ b/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts @@ -36,14 +36,14 @@ export class MetronDialogBox { private createDialogBox(message: string, type: DialogType) { let cancelButtonHTML = this.getCancelButton(type); - let html = `<div class="metron-dialog modal fade" data-backdrop="static" > + let html = `<div class="metron-dialog modal" data-backdrop="static" > <div class="modal-dialog modal-sm" role="document"> <div class="modal-content"> <div class="modal-header"> + <span class="modal-title"><b>` + MetronDialogBox.dialogType[type] + `</b></span> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> - <span class="modal-title"><b>` + MetronDialogBox.dialogType[type] + `</b></span> </div> <div class="modal-body"> <p>` + message + `</p> http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/shared/pipes/time-lapse.pipe.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/shared/pipes/time-lapse.pipe.ts b/metron-interface/metron-alerts/src/app/shared/pipes/time-lapse.pipe.ts new file mode 100644 index 0000000..5515ef5 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/shared/pipes/time-lapse.pipe.ts @@ -0,0 +1,17 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import * as moment from 'moment/moment'; + +@Pipe({ + name: 'timeLapse' +}) +export class TimeLapsePipe implements PipeTransform { + + transform(value: any): any { + if (isNaN(value)) { + return ''; + } + + return moment(new Date(value)).fromNow(); + } + +} http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/shared/shared.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/shared/shared.module.ts b/metron-interface/metron-alerts/src/app/shared/shared.module.ts index 41290a4..d5e4531 100644 --- a/metron-interface/metron-alerts/src/app/shared/shared.module.ts +++ b/metron-interface/metron-alerts/src/app/shared/shared.module.ts @@ -26,6 +26,7 @@ import { AlertSearchDirective } from './directives/alert-search.directive'; import { ColumnNameTranslatePipe } from './pipes/column-name-translate.pipe'; import { MapKeysPipe } from './pipes/map-keys.pipe'; import { AlertSeverityHexagonDirective } from './directives/alert-severity-hexagon.directive'; +import { TimeLapsePipe } from './pipes/time-lapse.pipe'; @NgModule({ imports: [ @@ -38,8 +39,9 @@ import { AlertSeverityHexagonDirective } from './directives/alert-severity-hexag CenterEllipsesPipe, AlertSearchDirective, ColumnNameTranslatePipe, + AlertSeverityHexagonDirective, + TimeLapsePipe, MapKeysPipe, - AlertSeverityHexagonDirective ], exports: [ CommonModule, @@ -50,8 +52,9 @@ import { AlertSeverityHexagonDirective } from './directives/alert-severity-hexag CenterEllipsesPipe, AlertSearchDirective, ColumnNameTranslatePipe, + AlertSeverityHexagonDirective, + TimeLapsePipe, MapKeysPipe, - AlertSeverityHexagonDirective ] }) export class SharedModule { } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts b/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts index 89f57a1..2600189 100644 --- a/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts +++ b/metron-interface/metron-alerts/src/app/shared/time-range/time-range.component.ts @@ -30,7 +30,7 @@ import {DateFilterValue} from '../../model/date-filter-value'; templateUrl: './time-range.component.html', styleUrls: ['./time-range.component.scss'] }) -export class TimeRangeComponent implements OnInit, OnChanges { +export class TimeRangeComponent implements OnChanges { toDateStr = ''; fromDateStr = ''; datePickerFromDate = ''; @@ -88,9 +88,6 @@ export class TimeRangeComponent implements OnInit, OnChanges { } } - ngOnInit() { - } - onSelectedTimeRangeChange() { let foundQuickRange = false; let merged = Object.assign({}, this.timeRangeMappingCol1, this.timeRangeMappingCol2, this.timeRangeMappingCol3, this.timeRangeMappingCol4); @@ -149,7 +146,7 @@ export class TimeRangeComponent implements OnInit, OnChanges { applyCustomDate() { this.hideDatePicker(); this.selectedTimeRangeValue = CUSTOMM_DATE_RANGE_LABEL; - this.toDateStr = this.datePickerToDate.length > 0 ? moment(this.datePickerToDate).format(DEFAULT_TIMESTAMP_FORMAT) : 'NOW'; + this.toDateStr = this.datePickerToDate.length > 0 ? moment(this.datePickerToDate).format(DEFAULT_TIMESTAMP_FORMAT) : 'now'; this.fromDateStr = moment(this.datePickerFromDate).format(DEFAULT_TIMESTAMP_FORMAT); let toDate = this.datePickerToDate.length > 0 ? new Date(this.toDateStr).getTime() : null; http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/utils/constants.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/utils/constants.ts b/metron-interface/metron-alerts/src/app/utils/constants.ts index b0a5a17..6fcea2b 100644 --- a/metron-interface/metron-alerts/src/app/utils/constants.ts +++ b/metron-interface/metron-alerts/src/app/utils/constants.ts @@ -18,6 +18,9 @@ import {environment} from '../../environments/environment'; +export const META_ALERTS_SENSOR_TYPE = 'metaalert'; +export const META_ALERTS_INDEX = 'metaalert_index'; + export const NUM_SAVED_SEARCH = 10; export const ALERTS_RECENT_SEARCH = 'metron-alerts-recent-saved-search'; export const ALERTS_SAVED_SEARCH = 'metron-alerts-saved-search'; @@ -35,3 +38,5 @@ export let TREE_SUB_GROUP_SIZE = 5; export let DEFAULT_FACETS = ['source:type', 'ip_src_addr', 'ip_dst_addr', 'host', 'enrichments:geo:ip_dst_addr:country']; export let DEFAULT_GROUPS = ['source:type', 'ip_src_addr', 'ip_dst_addr', 'host', 'enrichments:geo:ip_dst_addr:country']; export let INDEXES = environment.indices ? environment.indices.split(',') : []; + +export let MAX_ALERTS_IN_META_ALERTS = 350; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/utils/utils.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/utils/utils.ts b/metron-interface/metron-alerts/src/app/utils/utils.ts index 57a6355..34d2241 100644 --- a/metron-interface/metron-alerts/src/app/utils/utils.ts +++ b/metron-interface/metron-alerts/src/app/utils/utils.ts @@ -17,10 +17,29 @@ */ import * as moment from 'moment/moment'; -import {DEFAULT_TIMESTAMP_FORMAT, TIMESTAMP_FIELD_NAME} from './constants'; +import {DEFAULT_TIMESTAMP_FORMAT, META_ALERTS_SENSOR_TYPE} from './constants'; +import {Alert} from '../model/alert'; import {DateFilterValue} from '../model/date-filter-value'; export class Utils { + public static escapeESField(field: string): string { + return field.replace(/:/g, '\\:'); + } + + public static escapeESValue(value: string): string { + return String(value) + .replace(/[\*\+\-=~><\"\?^\${}\(\)\:\!\/[\]\\\s]/g, '\\$&') // replace single special characters + .replace(/\|\|/g, '\\||') // replace || + .replace(/\&\&/g, '\\&&'); // replace && + } + + public static getAlertSensorType(alert: Alert): string { + if (alert.source['source:type'] && alert.source['source:type'].length > 0) { + return alert.source['source:type']; + } else { + return META_ALERTS_SENSOR_TYPE; + } + } public static timeRangeToDateObj(range:string) { let timeRangeToDisplayStr = Utils.timeRangeToDisplayStr(range); @@ -181,4 +200,5 @@ export class Utils { return {toDate: toDate, fromDate: fromDate}; } + } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/environments/environment.e2e.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/environments/environment.e2e.ts b/metron-interface/metron-alerts/src/environments/environment.e2e.ts index 4a11048..df414b1 100644 --- a/metron-interface/metron-alerts/src/environments/environment.e2e.ts +++ b/metron-interface/metron-alerts/src/environments/environment.e2e.ts @@ -17,5 +17,5 @@ */ export const environment = { production: false, - indices: 'alerts_ui_e2e' -}; \ No newline at end of file + indices: 'alerts_ui_e2e,metaalert' +}; http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/styles.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/styles.scss b/metron-interface/metron-alerts/src/styles.scss index 0958685..e9e1d51 100644 --- a/metron-interface/metron-alerts/src/styles.scss +++ b/metron-interface/metron-alerts/src/styles.scss @@ -267,3 +267,62 @@ hr { margin: 0.3rem 0; padding: 0; } + +/** Custom Radio box **/ +$background_color_1: #eee; +$background_color_2: #ccc; +$background_color_3: #2196F3; +.radio-container { + display: block; + position: relative; + padding-left: 35px; + margin-bottom: 12px; + cursor: pointer; + font-size: 22px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + input { + position: absolute; + opacity: 0; + &:checked { + &~.checkmark { + background-color: $eastern-blue-2; + &:after { + display: block; + } + } + } + } + &:hover { + input { + &~.checkmark { + background-color: $eastern-blue-2; + } + } + } + .checkmark { + position: absolute; + top: 0; + left: 0; + height: 12px; + width: 12px; + background-color: $mine-shaft-2; + border: 1px solid $tundora; + border-radius: 50%; + + &:after { + top: 2px; + left: 2px; + width: 6px; + height: 6px; + border-radius: 50%; + background: $white; + content: ""; + position: absolute; + display: none; + } + } +} +
