You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by js...@apache.org on 2017/10/13 22:47:33 UTC
metron git commit: METRON-1223 Add support to add comments for alerts
(iraghumitra via james-sirota) closes apache/metron#788
Repository: metron
Updated Branches:
refs/heads/master bbfe29a97 -> 39bb85676
METRON-1223 Add support to add comments for alerts (iraghumitra via james-sirota) closes apache/metron#788
Project: http://git-wip-us.apache.org/repos/asf/metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/39bb8567
Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/39bb8567
Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/39bb8567
Branch: refs/heads/master
Commit: 39bb856762739ecd2103e3b67eed76d4fb655eaa
Parents: bbfe29a
Author: iraghumitra <ra...@gmail.com>
Authored: Fri Oct 13 15:47:17 2017 -0700
Committer: jsirota <js...@apache.org>
Committed: Fri Oct 13 15:47:17 2017 -0700
----------------------------------------------------------------------
metron-interface/metron-alerts/README.md | 5 +-
.../e2e/alert-details/alert-details.po.ts | 59 ++++++++++++-
.../alert-details-status.e2e-spec.ts | 34 +++++++-
.../e2e/alerts-list/alerts-list.e2e-spec.ts | 4 +-
.../e2e/alerts-list/alerts-list.po.ts | 4 +
.../metron-alerts/e2e/login/login.po.ts | 6 +-
.../metron-alerts/e2e/utils/e2e_util.ts | 16 +++-
metron-interface/metron-alerts/package.json | 2 +
.../metron-alerts/src/_variables.scss | 3 +
.../app/alerts/alert-details/alert-comment.ts | 29 +++++++
.../alert-details/alert-details.component.html | 91 ++++++++++++++------
.../alert-details/alert-details.component.scss | 77 ++++++++++++++++-
.../alert-details/alert-details.component.ts | 80 ++++++++++++++++-
.../alert-details/alerts-details.module.ts | 4 +-
.../alert-details/alerts-details.routing.ts | 2 +-
.../alerts/alerts-list/alerts-list.component.ts | 7 +-
.../table-view/table-view.component.html | 4 +-
.../src/app/login/login.component.html | 4 +-
.../src/app/login/login.component.scss | 4 +
.../src/app/login/login.component.ts | 2 +-
.../metron-alerts/src/app/model/alert-source.ts | 3 +
.../metron-alerts/src/app/model/alert.ts | 1 +
.../src/app/model/patch-request.ts | 6 +-
.../metron-alerts/src/app/model/patch.ts | 28 ++++++
.../src/app/service/authentication.service.ts | 4 +
metron-interface/metron-alerts/src/styles.scss | 10 ++-
26 files changed, 433 insertions(+), 56 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/README.md
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/README.md b/metron-interface/metron-alerts/README.md
index b0433d0..3d43e3e 100644
--- a/metron-interface/metron-alerts/README.md
+++ b/metron-interface/metron-alerts/README.md
@@ -12,6 +12,9 @@ UI uses local storage to save all the data. A middleware needs to be designed a
### Search for Alert GUIDs
Alert GUIDs must be double-quoted when being searched on to ensure correctness of results, e.g. guid:"id1".
+### Search for Comments
+Users cannot search for the contents of the comment's in the Alerts-UI
+
## Prerequisites
* The Metron REST application should be up and running and Elasticsearch should have some alerts populated by Metron topologies
* The Management UI should be installed (which includes [Express](https://expressjs.com/))
@@ -58,7 +61,7 @@ Alert GUIDs must be double-quoted when being searched on to ensure correctness o
### From Ambari MPack
-The Alerts UI is included in the Metron Ambari MPack. It can be accessed through the Quick Links in the Metron service.
+The Alerts UI is included in the Metron Ambari MPack. It can be accessed through the Quick Links in the Metron service.
## Configuration
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts b/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts
index 79a0e1d..39aea0b 100644
--- a/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts
+++ b/metron-interface/metron-alerts/e2e/alert-details/alert-details.po.ts
@@ -17,12 +17,25 @@
*/
import {browser, element, by, protractor} from 'protractor';
+import {waitForElementInVisibility, waitForElementPresence, waitForElementVisibility} from '../utils/e2e_util';
export class MetronAlertDetailsPage {
navigateTo(alertId: string) {
browser.waitForAngularEnabled(false);
- return browser.get('/alerts-list(dialog:details/alerts_ui_e2e/'+ alertId +')');
+ browser.get('/alerts-list(dialog:details/alerts_ui_e2e/'+ alertId +'/alerts_ui_e2e_index)');
+ browser.sleep(2000);
+ }
+
+ addCommentAndSave(comment: string, index: number) {
+ let textAreaElement = element(by.css('app-alert-details textarea'));
+ let addCommentButtonElement = element(by.buttonText('ADD COMMENT'));
+ let latestCommentEle = element.all(by.css('.comment-container .comment')).get(index);
+
+ textAreaElement.clear()
+ .then(() => textAreaElement.sendKeys(comment))
+ .then(() => addCommentButtonElement.click())
+ .then(() => waitForElementPresence(latestCommentEle));
}
clickNew() {
@@ -45,6 +58,38 @@ export class MetronAlertDetailsPage {
element.all(by.css('.metron-slider-pane-details table tbody tr')).get(2).all(by.css('td')).get(1).click();
}
+ clickCommentsInSideNav() {
+ return element(by.css('app-alert-details .fa.fa-comment')).click();
+ }
+
+ clickNoForConfirmation() {
+ browser.sleep(1000);
+ let dialogElement = element(by.css('.metron-dialog .modal-header .close'));
+ let maskElement = element(by.css('.modal-backdrop.fade'));
+ waitForElementVisibility(dialogElement).then(() => element(by.css('.metron-dialog')).element(by.buttonText('Cancel')).click())
+ .then(() => waitForElementInVisibility(maskElement));
+ }
+
+ clickYesForConfirmation() {
+ browser.sleep(1000);
+ let dialogElement = element(by.css('.metron-dialog .modal-header .close'));
+ let maskElement = element(by.css('.modal-backdrop.fade'));
+ waitForElementVisibility(dialogElement).then(() => element(by.css('.metron-dialog')).element(by.buttonText('OK')).click())
+ .then(() => waitForElementInVisibility(maskElement));
+ }
+
+ closeDetailPane() {
+ element(by.css('app-alert-details .close-button')).click();
+ browser.sleep(2000);
+ }
+
+ deleteComment() {
+ let scrollToEle = element.all(by.css('.comment-container')).get(0);
+ let trashIcon = element.all(by.css('.fa.fa-trash-o')).get(0);
+ browser.actions().mouseMove(scrollToEle).perform().then(() => waitForElementVisibility(trashIcon))
+ .then(() => element.all(by.css('.fa.fa-trash-o')).get(0).click());
+ }
+
getAlertStatus(previousText) {
let alertStatusElement = element.all(by.css('.metron-slider-pane-details .form .row')).get(0).all(by.css('div')).get(1);
return this.waitForTextChange(alertStatusElement, previousText).then(() => {
@@ -52,6 +97,18 @@ export class MetronAlertDetailsPage {
});
}
+ getCommentsText() {
+ return element.all(by.css('.comment-container .comment')).getText();
+ }
+
+ getCommentsUserNameAndTimeStamp() {
+ return element.all(by.css('.comment-container .username-timestamp')).getText();
+ }
+
+ getCommentIconCountInListView() {
+ return element.all(by.css('app-table-view .fa.fa-comments-o')).count();
+ }
+
waitForTextChange(element, previousText) {
let EC = protractor.ExpectedConditions;
return browser.wait(EC.not(EC.textToBePresentInElement(element, previousText)));
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts
index 4e7331c..58d4892 100644
--- a/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts
+++ b/metron-interface/metron-alerts/e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts
@@ -65,4 +65,36 @@ describe('metron-alerts alert status', function() {
page.clickNew();
});
-});
\ No newline at end of file
+ it('should add comments', () => {
+ let comment1 = 'This is a sample comment';
+ let comment2 = 'This is a sample comment again';
+ let userNameAndTimestamp = '- admin - a few seconds ago';
+
+ page.clickCommentsInSideNav();
+ page.addCommentAndSave(comment1, 0);
+
+ expect(page.getCommentsText()).toEqual([comment1]);
+ expect(page.getCommentsUserNameAndTimeStamp()).toEqual([userNameAndTimestamp]);
+
+ page.addCommentAndSave(comment2, 1);
+ expect(page.getCommentsText()).toEqual([comment2, comment1]);
+ expect(page.getCommentsUserNameAndTimeStamp()).toEqual([userNameAndTimestamp, userNameAndTimestamp]);
+
+ page.deleteComment();
+ page.clickNoForConfirmation();
+ expect(page.getCommentsText()).toEqual([comment2, comment1]);
+
+ page.deleteComment();
+ page.clickYesForConfirmation();
+ expect(page.getCommentsText()).toEqual([comment1]);
+
+ expect(page.getCommentIconCountInListView()).toEqual(1);
+
+ page.deleteComment();
+ page.clickYesForConfirmation();
+ expect(page.getCommentsText()).toEqual([]);
+
+ page.closeDetailPane();
+ });
+
+});
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts
index 8e92737..43290fe 100644
--- a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts
+++ b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.e2e-spec.ts
@@ -19,13 +19,13 @@
import { MetronAlertsPage } from './alerts-list.po';
import { customMatchers } from '../matchers/custom-matchers';
import { LoginPage } from '../login/login.po';
-import { loadTestData, deleteTestData } from "../utils/e2e_util";
+import { loadTestData, deleteTestData } from '../utils/e2e_util';
describe('metron-alerts App', function() {
let page: MetronAlertsPage;
let loginPage: LoginPage;
let columnNames = [ 'Score', 'id', 'timestamp', 'source:type', 'ip_src_addr', 'enrichm...:country',
- 'ip_dst_addr', 'host', 'alert_status', '', '' ];
+ 'ip_dst_addr', 'host', 'alert_status', '', '', '' ];
let colNamesColumnConfig = [ 'score', 'id', 'timestamp', 'source:type', 'ip_src_addr', 'enrichments:geo:ip_dst_addr:country',
'ip_dst_addr', 'host', 'alert_status' ];
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts
index 8e09f85..39aefa7 100644
--- a/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts
+++ b/metron-interface/metron-alerts/e2e/alerts-list/alerts-list.po.ts
@@ -184,6 +184,10 @@ export class MetronAlertsPage {
return element(by.css('.ace_line')).getText();
}
+ isCommentIconPresentInTable() {
+ return element.all(by.css('app-table-view .fa.fa-comments-o')).count();
+ }
+
getRecentSearchOptions() {
browser.sleep(1000);
let map = {};
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/e2e/login/login.po.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/login/login.po.ts b/metron-interface/metron-alerts/e2e/login/login.po.ts
index c9f8c23..2f0f81d 100644
--- a/metron-interface/metron-alerts/e2e/login/login.po.ts
+++ b/metron-interface/metron-alerts/e2e/login/login.po.ts
@@ -16,7 +16,7 @@
* limitations under the License.
*/
import { browser, element, by } from 'protractor';
-import {waitForElementVisibility} from '../utils/e2e_util';
+import {waitForElementVisibility, waitForURL} from '../utils/e2e_util';
export class LoginPage {
navigateToLogin() {
@@ -35,7 +35,7 @@ export class LoginPage {
browser.waitForAngularEnabled(false);
element.all(by.css('.alert .close')).click();
element.all(by.css('.logout-link')).click();
- browser.sleep(2000);
+ waitForURL('http://localhost:4200/login');
}
setUserNameAndPassword(userName: string, password: string) {
@@ -49,7 +49,7 @@ export class LoginPage {
getErrorMessage() {
browser.waitForAngularEnabled(false);
- let errElement = element(by.css('div[style="color:#a94442"]'));
+ let errElement = element(by.css('.login-failed-msg'));
return waitForElementVisibility(errElement).then(() => {
return errElement.getText().then((message) => {
return message.replace(/\n/, '').replace(/LOG\ IN$/, '');
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/utils/e2e_util.ts b/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
index ce5609a..341e668 100644
--- a/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
+++ b/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
@@ -10,6 +10,16 @@ export function changeURL(url: string) {
});
}
+export function waitForURL(url: string) {
+ let EC = protractor.ExpectedConditions;
+ return browser.wait(EC.urlIs(url));
+}
+
+export function waitForText(element, text) {
+ let EC = protractor.ExpectedConditions;
+ return browser.wait(EC.textToBePresentInElement(element, text));
+}
+
export function waitForElementInVisibility (_element ) {
let EC = protractor.ExpectedConditions;
return browser.wait(EC.invisibilityOf(_element));
@@ -32,8 +42,10 @@ export function waitForStalenessOf (_element ) {
export function loadTestData() {
deleteTestData();
- fs.createReadStream('e2e/mock-data/alerts_ui_e2e_index.template').pipe(request.post('http://node1:9200/_template/alerts_ui_e2e_index'));
- fs.createReadStream('e2e/mock-data/alerts_ui_e2e_index.data').pipe(request.post('http://node1:9200/alerts_ui_e2e_index/alerts_ui_e2e_doc/_bulk'));
+ fs.createReadStream('e2e/mock-data/alerts_ui_e2e_index.template')
+ .pipe(request.post('http://node1:9200/_template/alerts_ui_e2e_index'));
+ fs.createReadStream('e2e/mock-data/alerts_ui_e2e_index.data')
+ .pipe(request.post('http://node1:9200/alerts_ui_e2e_index/alerts_ui_e2e_doc/_bulk'));
}
export function deleteTestData() {
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/package.json
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/package.json b/metron-interface/metron-alerts/package.json
index 446c40d..6ff3c3c 100644
--- a/metron-interface/metron-alerts/package.json
+++ b/metron-interface/metron-alerts/package.json
@@ -25,6 +25,7 @@
"bootstrap": "4.0.0-alpha.6",
"core-js": "^2.4.1",
"font-awesome": "^4.7.0",
+ "moment": "^2.18.1",
"rxjs": "^5.1.0",
"web-animations-js": "^2.2.2",
"zone.js": "^0.8.4"
@@ -34,6 +35,7 @@
"@angular/compiler-cli": "^4.0.0",
"@types/ace": "0.0.32",
"@types/jasmine": "2.5.38",
+ "@types/moment": "^2.13.0",
"@types/node": "~6.0.60",
"codelyzer": "~2.0.0",
"compression": "1.6.2",
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/_variables.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/_variables.scss b/metron-interface/metron-alerts/src/_variables.scss
index 2705ff1..e4055ce 100644
--- a/metron-interface/metron-alerts/src/_variables.scss
+++ b/metron-interface/metron-alerts/src/_variables.scss
@@ -72,6 +72,9 @@ $eastern-blue: #1F91BE;
$mantis: #80BF4D;
$sky-blue: #75D2ED;
$outer-space: #2E3A3F;
+$rolling-stone: #808285;
+$nile-blue: #18404E;
+$apple-blossom: #A94442;
$eastern-blue-1: #1190C0;
$matisse: #1E7490;
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-comment.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-comment.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-comment.ts
new file mode 100644
index 0000000..a9cf802
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-comment.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 AlertComment {
+ comment = '';
+ username: string;
+ timestamp: number;
+
+ constructor(comment: string, username: string, timestamp: number) {
+ this.comment = comment;
+ this.username = username;
+ this.timestamp = timestamp;
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
index 8c82fab..1b5330e 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.html
@@ -12,35 +12,70 @@
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">
- <div class="row mb-3">
- <div class="col-md-10">
- <div class="form-title" appAlertSeverity [severity]="alertSource['threat:triage:score']"> {{ alertSource['threat:triage:score'] }} {{ alertSource.guid }}</div>
+ <div class="container-fluid pl-0 h-100">
+ <div class="h-100 d-flex">
+ <div class="nav-container">
+ <ul class="nav flex-column">
+ <li class="nav-item">
+ <a class="nav-link" [ngClass]="{'active': activeTab === tabs.DETAILS}" (click)="activeTab=tabs.DETAILS">
+ <i class="fa fa-info-circle" aria-hidden="true"></i>
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" [ngClass]="{'active': activeTab === tabs.COMMENTS}" (click)="activeTab=tabs.COMMENTS">
+ <i class="fa fa-comment" aria-hidden="true"></i>
+ </a>
+ </li>
+ </ul>
</div>
- <div class="col-md-2"><i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="goBack()"></i></div>
- </div>
- <div class="row justify-content-center py-4 actions">
- <table>
- <tr>
- <td class="title"> Status</td>
- <td [ngClass]="{'primary': selectedAlertState === alertState.ESCALATE, 'secondary': selectedAlertState !== alertState.ESCALATE}" (click)="processEscalate()">ESCALATE</td>
- <td></td>
- </tr>
- <tr>
- <td [ngClass]="{'primary': selectedAlertState === alertState.NEW, 'secondary': selectedAlertState !== alertState.NEW}" (click)="processNew()">NEW</td>
- <td [ngClass]="{'primary': selectedAlertState === alertState.OPEN, 'secondary': selectedAlertState !== alertState.OPEN}" (click)="processOpen()">OPEN</td>
- <td [ngClass]="{'primary': selectedAlertState === alertState.DISMISS, 'secondary': selectedAlertState !== alertState.DISMISS}" (click)="processDismiss()">DISMISS</td>
- </tr>
- <tr>
- <td></td>
- <td [ngClass]="{'primary': selectedAlertState === alertState.RESOLVE, 'secondary': selectedAlertState !== alertState.RESOLVE}" (click)="processResolve()">RESOLVE</td>
- <td></td>
- </tr>
- </table>
- </div>
- <div class="ml-1 my-4 form">
- <div *ngFor="let field of alertFields" class="row">
- <div class="col-6 mb-1 key">{{ field }}</div> <div class="col-6"> {{ alertSource[field] }} </div>
+ <div class="details-container">
+ <div class="container-fluid h-100">
+ <div class="row title-container">
+ <div class="col-md-10 px-0">
+ <div class="form-title" appAlertSeverity [severity]="alertSource['threat:triage:score']"> {{ alertSource['threat:triage:score'] }} {{ alertSource.guid }}</div>
+ </div>
+ <div class="col-md-2 px-0">
+ <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="goBack()"></i>
+ </div>
+ </div>
+ <div class="row justify-content-center py-4 actions">
+ <table>
+ <tr>
+ <td class="title"> Status</td>
+ <td [ngClass]="{'primary': selectedAlertState === alertState.ESCALATE, 'secondary': selectedAlertState !== alertState.ESCALATE}" data-name="escalate" (click)="processEscalate()">ESCALATE</td>
+ <td></td>
+ </tr>
+ <tr>
+ <td [ngClass]="{'primary': selectedAlertState === alertState.NEW, 'secondary': selectedAlertState !== alertState.NEW}" data-name="new" (click)="processNew()">NEW</td>
+ <td [ngClass]="{'primary': selectedAlertState === alertState.OPEN, 'secondary': selectedAlertState !== alertState.OPEN}" data-name="open" (click)="processOpen()">OPEN</td>
+ <td [ngClass]="{'primary': selectedAlertState === alertState.DISMISS, 'secondary': selectedAlertState !== alertState.DISMISS}" data-name="dismiss" (click)="processDismiss()">DISMISS</td>
+ </tr>
+ <tr>
+ <td></td>
+ <td [ngClass]="{'primary': selectedAlertState === alertState.RESOLVE, 'secondary': selectedAlertState !== alertState.RESOLVE}" data-name="resolve" (click)="processResolve()">RESOLVE</td>
+ <td></td>
+ </tr>
+ </table>
+ </div>
+ <div class="ml-1 my-4 form" *ngIf="activeTab === tabs.DETAILS">
+ <div *ngFor="let field of alertFields" class="row">
+ <div class="col-6 mb-1 key">{{ field }}</div> <div class="col-6 value"> {{ alertSource[field] }} </div>
+ </div>
+ </div>
+ <div *ngIf="activeTab === tabs.COMMENTS" class="my-4">
+ <div> Comments <span *ngIf="alertCommentsWrapper.length > 0"> ({{alertCommentsWrapper.length}}) </span></div>
+ <textarea class="form-control" [(ngModel)]="alertCommentStr"> </textarea>
+ <button class="btn btn-mine_shaft_2" [disabled]="alertCommentStr.trim().length === 0" (click)="onAddComment()">ADD COMMENT</button>
+ <ng-container *ngFor="let alertCommentWrapper of alertCommentsWrapper; let i = index">
+ <hr>
+ <div class="comment-container">
+ <i class="fa fa-trash-o" aria-hidden="true" (click)="onDeleteComment(i)"></i>
+ <div class="comment"> {{ alertCommentWrapper.alertComment.comment }} </div>
+ <div class="font-italic username-timestamp"> - {{ alertCommentWrapper.alertComment.username }} - {{alertCommentWrapper.displayTime}}</div>
+ </div>
+ </ng-container>
+ </div>
+ </div>
</div>
</div>
</div>
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
index 8407bba..5899140 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.scss
@@ -18,8 +18,8 @@
@import "../../../slider";
@import "../../../variables";
-.container-fluid {
- padding-top: 15px;
+.title-container {
+ margin: 13px 0px;
}
.form-title {
@@ -70,3 +70,76 @@
color: $dove-grey;
}
}
+
+.dialog1x {
+ width: 400px;
+}
+
+.nav-container {
+ min-height: 99vh;
+ width: 54px;
+ background: $mine-shaft-3;
+ border-right: 1px solid $dove-grey;
+
+ i {
+ cursor: pointer;
+ font-size: 19px;
+ color: $dusty-grey;
+ }
+
+ .active i {
+ color: #FFFFFF;
+ }
+
+ .nav-link {
+ padding: 15px 1em;
+
+ &.active {
+ border-left: 4px solid #31ABDF;
+ background: $mine-shaft-2;
+ }
+ }
+}
+
+.details-container {
+ width: 340px;
+}
+
+textarea {
+ margin-top: 5px;
+ height: 100px;
+}
+
+.btn-mine_shaft_2 {
+ margin-top: 10px;
+ margin-bottom: 15px;
+}
+
+.username-timestamp {
+ color: $rolling-stone;
+}
+
+.comment-container {
+ font-size: 14px;
+ padding: 10px 0px;
+
+ i {
+ right: 25px;
+ display: none;
+ cursor: pointer;
+ font-size: 18px;
+ position: absolute;
+ }
+
+ .comment {
+ padding-right: 25px;
+ }
+
+ &:hover {
+ background: $nile-blue;
+ }
+}
+
+.comment-container:hover i {
+ display: block;
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts
index 2efb2ce..3d5b70e 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details.component.ts
@@ -17,16 +17,37 @@
*/
import { Component, OnInit } from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';
+import * as moment from 'moment/moment';
+
import {SearchService} from '../../service/search.service';
import {UpdateService} from '../../service/update.service';
import {Alert} from '../../model/alert';
import {AlertsService} from '../../service/alerts.service';
import {AlertSource} from '../../model/alert-source';
+import {PatchRequest} from '../../model/patch-request';
+import {Patch} from '../../model/patch';
+import {AlertComment} from './alert-comment';
+import {AuthenticationService} from '../../service/authentication.service';
+import {MetronDialogBox} from '../../shared/metron-dialog-box';
export enum AlertState {
NEW, OPEN, ESCALATE, DISMISS, RESOLVE
}
+export enum Tabs {
+ DETAILS, COMMENTS
+}
+
+class AlertCommentWrapper {
+ alertComment: AlertComment;
+ displayTime: string;
+
+ constructor(alertComment: AlertComment, displayTime: string) {
+ this.alertComment = alertComment;
+ this.displayTime = displayTime;
+ }
+}
+
@Component({
selector: 'app-alert-details',
templateUrl: './alert-details.component.html',
@@ -36,16 +57,25 @@ export class AlertDetailsComponent implements OnInit {
alertId = '';
alertSourceType = '';
+ alertIndex = '';
alertState = AlertState;
+ tabs = Tabs;
+ activeTab = Tabs.DETAILS;
selectedAlertState: AlertState = AlertState.NEW;
alertSource: AlertSource = new AlertSource();
alertFields: string[] = [];
+ alertCommentStr = '';
+ alertCommentsWrapper: AlertCommentWrapper[] = [];
constructor(private router: Router,
private activatedRoute: ActivatedRoute,
private searchService: SearchService,
private updateService: UpdateService,
- private alertsService: AlertsService) { }
+ private alertsService: AlertsService,
+ private authenticationService: AuthenticationService,
+ private metronDialogBox: MetronDialogBox) {
+
+ }
goBack() {
this.router.navigateByUrl('/alerts-list');
@@ -53,13 +83,22 @@ export class AlertDetailsComponent implements OnInit {
}
getData() {
+ this.alertCommentStr = '';
this.searchService.getAlert(this.alertSourceType, this.alertId).subscribe(alert => {
this.alertSource = alert;
- this.alertFields = Object.keys(alert).filter(field => !field.includes(':ts') && field !== 'original_string').sort();
+ this.alertFields = Object.keys(alert).filter(field => !field.includes(':ts') && field !== 'original_string' && field !== 'comments')
+ .sort();
this.selectedAlertState = this.getAlertState(alert['alert_status']);
+ this.setComments(alert);
});
}
+ setComments(alert) {
+ let alertComments = alert['comments'] ? alert['comments'] : [];
+ this.alertCommentsWrapper = alertComments.map(alertComment =>
+ new AlertCommentWrapper(alertComment, moment(new Date(alertComment.timestamp)).fromNow()));
+ }
+
getAlertState(alertStatus) {
if (alertStatus === 'OPEN') {
return AlertState.OPEN;
@@ -78,9 +117,10 @@ export class AlertDetailsComponent implements OnInit {
this.activatedRoute.params.subscribe(params => {
this.alertId = params['guid'];
this.alertSourceType = params['sourceType'];
+ this.alertIndex = params['index'];
this.getData();
});
- }
+ };
processOpen() {
let tAlert = new Alert();
@@ -133,6 +173,40 @@ export class AlertDetailsComponent implements OnInit {
});
}
+ onAddComment() {
+ let alertComment = new AlertComment(this.alertCommentStr, this.authenticationService.getCurrentUserName(), new Date().getTime());
+ let tAlertComments = this.alertCommentsWrapper.map(alertsWrapper => alertsWrapper.alertComment);
+ tAlertComments.unshift(alertComment);
+ this.patchAlert(new Patch('add', '/comments', tAlertComments));
+ }
+
+ patchAlert(patch: Patch) {
+ let patchRequest = new PatchRequest();
+ patchRequest.guid = this.alertSource.guid;
+ patchRequest.index = this.alertIndex;
+ patchRequest.patch = [patch];
+ patchRequest.sensorType = this.alertSourceType;
+
+ this.updateService.patch(patchRequest).subscribe(() => {
+ this.getData();
+ });
+ }
+
+ onDeleteComment(index: number) {
+ let commentText = 'Do you wish to delete the comment ';
+ if (this.alertCommentsWrapper[index].alertComment.comment.length > 25 ) {
+ commentText += ' \'' + this.alertCommentsWrapper[index].alertComment.comment.substr(0, 25) + '...\'';
+ } else {
+ commentText += ' \'' + this.alertCommentsWrapper[index].alertComment.comment + '\'';
+ }
+
+ this.metronDialogBox.showConfirmationMessage(commentText).subscribe(response => {
+ if (response) {
+ this.alertCommentsWrapper.splice(index, 1);
+ this.patchAlert(new Patch('add', '/comments', this.alertCommentsWrapper.map(alertsWrapper => alertsWrapper.alertComment)));
+ }
+ });
+ }
}
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts
index 9b4927e..d1e36a6 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.module.ts
@@ -20,11 +20,11 @@ import {routing} from './alerts-details.routing';
import {SharedModule} from '../../shared/shared.module';
import {AlertDetailsComponent} from './alert-details.component';
import {AlertsService} from '../../service/alerts.service';
-import {UpdateService} from '../../service/update.service';
+import {AuthenticationService} from '../../service/authentication.service';
@NgModule ({
imports: [ routing, SharedModule],
declarations: [ AlertDetailsComponent ],
- providers: [ AlertsService, UpdateService ],
+ providers: [ AuthenticationService, AlertsService ]
})
export class AlertDetailsModule { }
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.routing.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.routing.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.routing.ts
index bc4dd5b..0cb9c9c 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.routing.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alerts-details.routing.ts
@@ -20,5 +20,5 @@ import { RouterModule } from '@angular/router';
import {AlertDetailsComponent} from './alert-details.component';
export const routing: ModuleWithProviders = RouterModule.forChild([
- { path: 'details/:sourceType/:guid', component: AlertDetailsComponent, outlet: 'dialog'}
+ { path: 'details/:sourceType/:guid/:index', component: AlertDetailsComponent, outlet: 'dialog'}
]);
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/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 189c9ba..46b7796 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
@@ -314,10 +314,11 @@ export class AlertsListComponent implements OnInit, OnDestroy {
}
showDetails(alert: Alert) {
+ let url = '/alerts-list(dialog:details/' + alert.source['source:type'] + '/' + alert.source.guid + '/' + alert.index + ')';
this.selectedAlerts = [];
this.selectedAlerts = [alert];
this.saveRefreshState();
- this.router.navigateByUrl('/alerts-list(dialog:details/' + alert.source['source:type'] + '/' + alert.source.guid + ')');
+ this.router.navigateByUrl(url);
}
saveRefreshState() {
@@ -367,7 +368,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
updateAlert(patchRequest: PatchRequest) {
this.searchService.getAlert(patchRequest.sensorType, patchRequest.guid).subscribe(alertSource => {
- this.alerts.filter(alert => alert.source.guid == patchRequest.guid)
+ this.alerts.filter(alert => alert.source.guid === patchRequest.guid)
.map(alert => alert.source = alertSource);
});
}
@@ -375,7 +376,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
updateSelectedAlertStatus(status: string) {
for (let selectedAlert of this.selectedAlerts) {
selectedAlert.status = status;
- this.alerts.filter(alert => alert.source.guid == selectedAlert.source.guid)
+ this.alerts.filter(alert => alert.source.guid === selectedAlert.source.guid)
.map(alert => alert.source['alert_status'] = status);
}
this.selectedAlerts = [];
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/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
index ae788fc..561b299 100644
--- 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
@@ -17,6 +17,7 @@
<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></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>
@@ -29,7 +30,8 @@
<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> <i class="fa fa-comments-o" aria-hidden="true" *ngIf="alert.source.comments && alert.source.comments.length > 0"></i> </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>
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/login/login.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/login/login.component.html b/metron-interface/metron-alerts/src/app/login/login.component.html
index 5ae746f..edf0c8d 100644
--- a/metron-interface/metron-alerts/src/app/login/login.component.html
+++ b/metron-interface/metron-alerts/src/app/login/login.component.html
@@ -20,8 +20,8 @@
<input class="form-control" name="user" [(ngModel)]="user" required autofocus>
<label class="label"> PASSWORD </label>
<input type="password" name="password" class="form-control" [(ngModel)]="password" required>
- <div class="my-4" style="color:#a94442">
- {{loginFailure}}
+ <div class="my-4">
+ <div class="login-failed-msg" *ngIf="loginFailure.length > 0">{{loginFailure}}</div>
<button class="btn btn-primary pull-right" type="submit" (click)="login()">LOG IN</button>
</div>
</form>
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/login/login.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/login/login.component.scss b/metron-interface/metron-alerts/src/app/login/login.component.scss
index b8d5416..d11c433 100644
--- a/metron-interface/metron-alerts/src/app/login/login.component.scss
+++ b/metron-interface/metron-alerts/src/app/login/login.component.scss
@@ -53,3 +53,7 @@
width: 390px;
height: 120px;
}
+
+.login-failed-msg {
+ color: $apple-blossom;
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/login/login.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/login/login.component.ts b/metron-interface/metron-alerts/src/app/login/login.component.ts
index 1d6c24f..9b6d66c 100644
--- a/metron-interface/metron-alerts/src/app/login/login.component.ts
+++ b/metron-interface/metron-alerts/src/app/login/login.component.ts
@@ -28,7 +28,7 @@ export class LoginComponent {
user: string;
password: string;
- loginFailure: string = '';
+ loginFailure = '';
constructor(private authenticationService: AuthenticationService, private activatedRoute: ActivatedRoute) {
this.activatedRoute.queryParams.subscribe(params => {
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/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 cbf83d3..4e3a655 100644
--- a/metron-interface/metron-alerts/src/app/model/alert-source.ts
+++ b/metron-interface/metron-alerts/src/app/model/alert-source.ts
@@ -15,6 +15,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import {AlertComment} from '../alerts/alert-details/alert-comment';
+
export class AlertSource {
msg: string;
sig_rev: number;
@@ -42,6 +44,7 @@ export class AlertSource {
guid: string;
sig_id: number;
sig_generator: number;
+ comments: AlertComment[] = [];
'threat:triage:score': number;
'threatinteljoinbolt:joiner:ts': number;
'enrichmentsplitterbolt:splitter:begin:ts': number;
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/model/alert.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/model/alert.ts b/metron-interface/metron-alerts/src/app/model/alert.ts
index 59843a0..5a4e73b 100644
--- a/metron-interface/metron-alerts/src/app/model/alert.ts
+++ b/metron-interface/metron-alerts/src/app/model/alert.ts
@@ -21,4 +21,5 @@ export class Alert {
score: number;
source: AlertSource;
status: string;
+ index: string;
}
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/model/patch-request.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/model/patch-request.ts b/metron-interface/metron-alerts/src/app/model/patch-request.ts
index 8e984ab..79db2d7 100644
--- a/metron-interface/metron-alerts/src/app/model/patch-request.ts
+++ b/metron-interface/metron-alerts/src/app/model/patch-request.ts
@@ -15,10 +15,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import {Patch} from './patch';
+
export class PatchRequest {
guid: string;
sensorType: string;
index: string;
- patch: {};
+ patch: Patch[] = [];
source: {};
-}
\ No newline at end of file
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/model/patch.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/model/patch.ts b/metron-interface/metron-alerts/src/app/model/patch.ts
new file mode 100644
index 0000000..4ce29f1
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/model/patch.ts
@@ -0,0 +1,28 @@
+/**
+ * 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 Patch {
+ op: 'add' | 'remove';
+ path: string;
+ value: any;
+
+ constructor(op, path: string, value: any) {
+ this.op = op;
+ this.path = path;
+ this.value = value;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/metron-interface/metron-alerts/src/app/service/authentication.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/service/authentication.service.ts b/metron-interface/metron-alerts/src/app/service/authentication.service.ts
index f4924a5..e66725d 100644
--- a/metron-interface/metron-alerts/src/app/service/authentication.service.ts
+++ b/metron-interface/metron-alerts/src/app/service/authentication.service.ts
@@ -81,6 +81,10 @@ export class AuthenticationService {
return this.http.get(this.loginUrl, options);
}
+ public getCurrentUserName(): string {
+ return this.currentUser;
+ }
+
public isAuthenticationChecked(): boolean {
return this.currentUser !== AuthenticationService.USER_NOT_VERIFIED;
}
http://git-wip-us.apache.org/repos/asf/metron/blob/39bb8567/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 12ac9f7..e3a0622 100644
--- a/metron-interface/metron-alerts/src/styles.scss
+++ b/metron-interface/metron-alerts/src/styles.scss
@@ -235,10 +235,18 @@ form
color: $piction-blue;
font-size: 14px;
min-width: 85px;
- width: 85px;
cursor: pointer;
&:focus
{
outline: none;
}
+}
+
+hr {
+ display: block;
+ height: 1px;
+ border: 0;
+ border-top: 1px solid $tundora-1;
+ margin: 0.3rem 0;
+ padding: 0;
}
\ No newline at end of file