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/14 14:46:00 UTC
[2/2] metron git commit: METRON-1123 Add group by option using
faceted search capabilities of metron-rest-api (iraghumitra via james-sirota)
closes apache/metron#768
METRON-1123 Add group by option using faceted search capabilities of metron-rest-api (iraghumitra via james-sirota) closes apache/metron#768
Project: http://git-wip-us.apache.org/repos/asf/metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/942aaaf2
Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/942aaaf2
Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/942aaaf2
Branch: refs/heads/master
Commit: 942aaaf2219d58af4d08744b49a90e7cf34b751c
Parents: 39bb856
Author: iraghumitra <ra...@gmail.com>
Authored: Sat Oct 14 07:44:27 2017 -0700
Committer: jsirota <js...@apache.org>
Committed: Sat Oct 14 07:44:27 2017 -0700
----------------------------------------------------------------------
metron-interface/metron-alerts/angular-cli.json | 1 +
.../e2e/alert-details/alert-details.po.ts | 4 +
.../alert-details-status.e2e-spec.ts | 47 ++-
.../alert-status/alerts-list-status.e2e-spec.ts | 42 +++
.../e2e/alerts-list/alerts-list.e2e-spec.ts | 2 +-
.../e2e/alerts-list/alerts-list.po.ts | 11 +-
.../alerts-list/tree-view/tree-view.e2e-spec.ts | 236 +++++++++++++
.../e2e/alerts-list/tree-view/tree-view.po.ts | 162 +++++++++
.../metron-alerts/e2e/login/login.po.ts | 1 +
.../metron-alerts/e2e/utils/e2e_util.ts | 7 +-
metron-interface/metron-alerts/package.json | 1 +
.../metron-alerts/protractor.conf.js | 1 +
.../metron-alerts/src/_hexagon.scss | 91 +++++
.../metron-alerts/src/_variables.scss | 16 +
.../alerts-list/alerts-list.component.html | 32 +-
.../alerts-list/alerts-list.component.scss | 2 +-
.../alerts/alerts-list/alerts-list.component.ts | 44 ++-
.../alerts/alerts-list/alerts-list.module.ts | 9 +-
.../src/app/alerts/alerts-list/query-builder.ts | 12 +
.../table-view/table-view.component.html | 2 -
.../table-view/table-view.component.scss | 2 +-
.../alerts-list/tree-view/tree-group-data.ts | 65 ++++
.../tree-view/tree-view.component.html | 114 ++++++
.../tree-view/tree-view.component.scss | 153 ++++++++
.../tree-view/tree-view.component.spec.ts | 25 ++
.../tree-view/tree-view.component.ts | 352 +++++++++++++++++++
.../configure-rows.component.scss | 4 +-
.../metron-alerts/src/app/app.component.ts | 21 +-
.../metron-alerts/src/app/model/group-order.ts | 22 ++
.../src/app/model/group-request.ts | 26 ++
.../src/app/model/group-response.ts | 23 ++
.../metron-alerts/src/app/model/group-result.ts | 25 ++
.../metron-alerts/src/app/model/group.ts | 29 ++
.../src/app/model/search-request.ts | 8 +-
.../src/app/model/search-response.ts | 4 +
.../src/app/model/search-result-group.ts | 26 ++
.../src/app/service/search.service.ts | 13 +-
.../alert-severity-hexagon.directive.spec.ts | 8 +
.../alert-severity-hexagon.directive.ts | 45 +++
.../directives/alert-severity.directive.ts | 12 +-
.../shared/group-by/group-by-component-data.ts | 28 ++
.../app/shared/group-by/group-by.component.html | 28 ++
.../app/shared/group-by/group-by.component.scss | 132 +++++++
.../app/shared/group-by/group-by.component.ts | 92 +++++
.../src/app/shared/group-by/group-by.module.ts | 35 ++
.../metron-sorter/metron-sorter.component.ts | 16 +-
.../metron-table/metron-table.directive.ts | 4 +-
.../src/app/shared/shared.module.ts | 7 +-
.../metron-alerts/src/app/utils/constants.ts | 8 +-
.../src/app/utils/elasticsearch-utils.ts | 8 +-
metron-interface/metron-alerts/src/styles.scss | 12 +-
51 files changed, 2002 insertions(+), 68 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/angular-cli.json
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/angular-cli.json b/metron-interface/metron-alerts/angular-cli.json
index 141f29b..833a778 100644
--- a/metron-interface/metron-alerts/angular-cli.json
+++ b/metron-interface/metron-alerts/angular-cli.json
@@ -20,6 +20,7 @@
"prefix": "app",
"styles": [
"../node_modules/font-awesome/css/font-awesome.css",
+ "../node_modules/dragula/dist/dragula.css",
"vendor.scss",
"styles.scss"
],
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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 39aea0b..ed71627 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
@@ -109,6 +109,10 @@ export class MetronAlertDetailsPage {
return element.all(by.css('app-table-view .fa.fa-comments-o')).count();
}
+ getCommentIconCountInTreeView() {
+ return element.all(by.css('app-tree-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/942aaaf2/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 58d4892..4e25f82 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
@@ -21,16 +21,19 @@ import {customMatchers} from '../../matchers/custom-matchers';
import {LoginPage} from '../../login/login.po';
import {loadTestData, deleteTestData} from '../../utils/e2e_util';
import { MetronAlertsPage } from '../../alerts-list/alerts-list.po';
+import {TreeViewPage} from '../../alerts-list/tree-view/tree-view.po';
describe('metron-alerts alert status', function() {
let page: MetronAlertDetailsPage;
let listPage: MetronAlertsPage;
+ let treePage: TreeViewPage;
let loginPage: LoginPage;
beforeAll(() => {
loadTestData();
loginPage = new LoginPage();
listPage = new MetronAlertsPage();
+ treePage = new TreeViewPage();
loginPage.login();
});
@@ -65,7 +68,7 @@ describe('metron-alerts alert status', function() {
page.clickNew();
});
- it('should add comments', () => {
+ it('should add comments for table view', () => {
let comment1 = 'This is a sample comment';
let comment2 = 'This is a sample comment again';
let userNameAndTimestamp = '- admin - a few seconds ago';
@@ -97,4 +100,46 @@ describe('metron-alerts alert status', function() {
page.closeDetailPane();
});
+ it('should add comments for tree view', () => {
+ let comment1 = 'This is a sample comment';
+ let comment2 = 'This is a sample comment again';
+ let userNameAndTimestamp = '- admin - a few seconds ago';
+
+ treePage.selectGroup('source:type');
+ treePage.expandDashGroup('alerts_ui_e2e');
+
+ treePage.clickOnRow('acf5a641-9cdb-d7ec-c309-6ea316e14fbe');
+ page.clickCommentsInSideNav();
+ page.addCommentAndSave(comment1, 0);
+
+ expect(page.getCommentsText()).toEqual([comment1]);
+ expect(page.getCommentsUserNameAndTimeStamp()).toEqual([userNameAndTimestamp]);
+ expect(page.getCommentIconCountInTreeView()).toEqual(1);
+
+ page.deleteComment();
+ page.clickYesForConfirmation();
+ expect(page.getCommentsText()).toEqual([]);
+ page.closeDetailPane();
+
+ treePage.unGroup();
+
+ treePage.selectGroup('source:type');
+ treePage.selectGroup('enrichments:geo:ip_dst_addr:country');
+ treePage.expandDashGroup('alerts_ui_e2e');
+ treePage.expandSubGroup('alerts_ui_e2e', 'FR');
+
+ treePage.clickOnRow('7cd91565-132f-3340-db76-3ade5be54a6e');
+ page.clickCommentsInSideNav();
+ page.addCommentAndSave(comment2, 0);
+
+ expect(page.getCommentsText()).toEqual([comment2]);
+ expect(page.getCommentsUserNameAndTimeStamp()).toEqual([userNameAndTimestamp]);
+ expect(page.getCommentIconCountInTreeView()).toEqual(1);
+
+ page.deleteComment();
+ page.clickYesForConfirmation();
+ expect(page.getCommentsText()).toEqual([]);
+ page.closeDetailPane();
+ });
+
});
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts
index e793d6f..828f290 100644
--- a/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts
+++ b/metron-interface/metron-alerts/e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts
@@ -20,9 +20,11 @@ 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 {TreeViewPage} from '../tree-view/tree-view.po';
describe('metron-alerts alert status', function() {
let page: MetronAlertsPage;
+ let treePage: TreeViewPage;
let loginPage: LoginPage;
beforeAll(() => {
@@ -38,6 +40,7 @@ describe('metron-alerts alert status', function() {
beforeEach(() => {
page = new MetronAlertsPage();
+ treePage = new TreeViewPage();
jasmine.addMatchers(customMatchers);
});
@@ -82,4 +85,43 @@ describe('metron-alerts alert status', function() {
expect(page.getAlertStatus(11, 'NEW')).toEqual('RESOLVE');
});
+
+ it('should change alert status for multiple alerts to OPEN in tree view', () => {
+ treePage.selectGroup('source:type');
+ treePage.selectGroup('enrichments:geo:ip_dst_addr:country');
+
+ treePage.expandDashGroup('alerts_ui_e2e');
+ treePage.expandSubGroup('alerts_ui_e2e', 'US');
+ treePage.expandSubGroup('alerts_ui_e2e', 'RU');
+ treePage.expandSubGroup('alerts_ui_e2e', 'FR');
+
+ treePage.toggleAlertInTree(1);
+ treePage.toggleAlertInTree(2);
+ treePage.toggleAlertInTree(3);
+ page.clickActionDropdownOption('Open');
+ expect(treePage.getAlertStatusForTreeView(1, 'NEW')).toEqual('OPEN');
+ expect(treePage.getAlertStatusForTreeView(2, 'NEW')).toEqual('OPEN');
+ expect(treePage.getAlertStatusForTreeView(3, 'NEW')).toEqual('OPEN');
+
+ treePage.toggleAlertInTree(4);
+ treePage.toggleAlertInTree(5);
+ page.clickActionDropdownOption('Dismiss');
+ expect(treePage.getAlertStatusForTreeView(4, 'NEW')).toEqual('DISMISS');
+ expect(treePage.getAlertStatusForTreeView(5, 'NEW')).toEqual('DISMISS');
+
+ treePage.toggleAlertInTree(8);
+ treePage.toggleAlertInTree(9);
+ page.clickActionDropdownOption('Escalate');
+ expect(treePage.getAlertStatusForTreeView(8, 'NEW')).toEqual('ESCALATE');
+ expect(treePage.getAlertStatusForTreeView(9, 'NEW')).toEqual('ESCALATE');
+
+ treePage.toggleAlertInTree(10);
+ treePage.toggleAlertInTree(11);
+ treePage.toggleAlertInTree(12);
+ page.clickActionDropdownOption('Resolve');
+ expect(treePage.getAlertStatusForTreeView(10, 'NEW')).toEqual('RESOLVE');
+ expect(treePage.getAlertStatusForTreeView(11, 'NEW')).toEqual('RESOLVE');
+ expect(treePage.getAlertStatusForTreeView(12, 'NEW')).toEqual('RESOLVE');
+ });
+
});
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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 43290fe..6b2ffd0 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
@@ -25,7 +25,7 @@ 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/942aaaf2/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 39aefa7..7fee303 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
@@ -17,6 +17,7 @@
*/
import {browser, element, by, protractor} from 'protractor';
+import {waitForElementVisibility, waitForElementPresence} from '../utils/e2e_util';
export class MetronAlertsPage {
navigateTo() {
@@ -69,12 +70,15 @@ export class MetronAlertsPage {
}
clickActionDropdown() {
- return element(by.buttonText('ACTIONS')).click();
+ let actionsDropDown = element(by.buttonText('ACTIONS'));
+ browser.actions().mouseMove(actionsDropDown).perform();
+ return actionsDropDown.click();
}
clickActionDropdownOption(option: string) {
this.clickActionDropdown().then(() => {
element(by.cssContainingText('.dropdown-menu span', option)).click();
+ browser.sleep(2000);
});
}
@@ -115,7 +119,8 @@ export class MetronAlertsPage {
}
clickSettings() {
- return element(by.css('.btn.settings')).click();
+ let settingsIcon = element(by.css('.btn.settings'));
+ return waitForElementVisibility(settingsIcon).then(() => settingsIcon.click());
}
getSettingsLabels() {
@@ -165,7 +170,7 @@ export class MetronAlertsPage {
}
clickTableText(name: string) {
- element.all(by.linkText(name)).get(0).click();
+ waitForElementPresence(element.all(by.css('app-table-view tbody tr'))).then(() => element.all(by.linkText(name)).get(0).click());
}
clickClearSearch() {
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.e2e-spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.e2e-spec.ts
new file mode 100644
index 0000000..caa3754
--- /dev/null
+++ b/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.e2e-spec.ts
@@ -0,0 +1,236 @@
+/// <reference path="../../matchers/custom-matchers.d.ts"/>
+/**
+ * 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 {customMatchers} from '../../matchers/custom-matchers';
+import {LoginPage} from '../../login/login.po';
+import {TreeViewPage} from './tree-view.po';
+import {loadTestData, deleteTestData} from '../../utils/e2e_util';
+import {MetronAlertsPage} from '../alerts-list.po';
+
+describe('metron-alerts tree view', function () {
+ let page: TreeViewPage;
+ let listPage: MetronAlertsPage;
+ let loginPage: LoginPage;
+
+ beforeAll(() => {
+ loadTestData();
+ loginPage = new LoginPage();
+ page = new TreeViewPage();
+ listPage = new MetronAlertsPage();
+ loginPage.login();
+ page.navigateToAlertsList();
+ });
+
+ afterAll(() => {
+ loginPage.logout();
+ deleteTestData();
+ });
+
+ beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+ });
+
+ it('should have all group by elements', () => {
+ let groupByItems = {
+ 'source:type': '1',
+ 'ip_dst_addr': '8',
+ 'host': '9',
+ 'enrichm...:country': '3',
+ 'ip_src_addr': '2'
+ };
+ expect(page.getGroupByCount()).toEqualBcoz(Object.keys(groupByItems).length, '5 Group By Elements should be present');
+ expect(page.getGroupByItemNames()).toEqualBcoz(Object.keys(groupByItems), 'Group By Elements names should be present');
+ expect(page.getGroupByItemCounts()).toEqualBcoz(Object.keys(groupByItems).map(key => groupByItems[key]),
+ '5 Group By Elements values should be present');
+ });
+
+ it('drag and drop should change group order', () => {
+ let before = {
+ 'firstDashRow': ['0', 'alerts_ui_e2e', 'ALERTS', '169'],
+ 'firstSubGroup': '0 US (22)',
+ 'secondSubGroup': '0 RU (44)',
+ 'thirdSubGroup': '0 FR (25)'
+ };
+
+ let after = {
+ 'firstDashRow': ['0', 'US', 'ALERTS', '22'],
+ 'secondDashRow': ['0', 'RU', 'ALERTS', '44'],
+ 'thirdDashRow': ['0', 'FR', 'ALERTS', '25'],
+ 'firstDashSubGroup': '0 alerts_ui_e2e (22)',
+ 'secondDashSubGroup': '0 alerts_ui_e2e (44)',
+ 'thirdDashSubGroup': '0 alerts_ui_e2e (25)'
+ };
+
+ page.selectGroup('source:type');
+ page.selectGroup('enrichments:geo:ip_dst_addr:country');
+ page.expandDashGroup('alerts_ui_e2e');
+ expect(page.getDashGroupValues('alerts_ui_e2e')).toEqualBcoz(before.firstDashRow, 'First Dash Row should be correct');
+ expect(page.getSubGroupValues('alerts_ui_e2e', 'US')).toEqualBcoz(before.firstSubGroup,
+ 'Dash Group Values should be correct for US');
+ expect(page.getSubGroupValues('alerts_ui_e2e', 'RU')).toEqualBcoz(before.secondSubGroup,
+ 'Dash Group Values should be present for RU');
+ expect(page.getSubGroupValues('alerts_ui_e2e', 'FR')).toEqualBcoz(before.thirdSubGroup,
+ 'Dash Group Values should be present for FR');
+
+ page.dragGroup('source:type', 'ip_src_addr');
+ //page.selectGroup('source:type');
+ expect(page.getDashGroupValues('US')).toEqualBcoz(after.firstDashRow, 'First Dash Row after ' +
+ 'reorder should be correct');
+ expect(page.getDashGroupValues('RU')).toEqualBcoz(after.secondDashRow, 'Second Dash Row after ' +
+ 'reorder should be correct');
+ expect(page.getDashGroupValues('FR')).toEqualBcoz(after.thirdDashRow, 'Third Dash Row after ' +
+ 'reorder should be correct');
+
+ page.expandDashGroup('US');
+ expect(page.getSubGroupValues('US', 'alerts_ui_e2e')).toEqualBcoz(after.firstDashSubGroup,
+ 'First Dash Group Values should be present for alerts_ui_e2e');
+
+ page.expandDashGroup('RU');
+ expect(page.getSubGroupValues('RU', 'alerts_ui_e2e')).toEqualBcoz(after.secondDashSubGroup,
+ 'Second Dash Group Values should be present for alerts_ui_e2e');
+
+ page.expandDashGroup('FR');
+ expect(page.getSubGroupValues('FR', 'alerts_ui_e2e')).toEqualBcoz(after.thirdDashSubGroup,
+ 'Third Dash Group Values should be present for alerts_ui_e2e');
+
+ page.dragGroup('source:type', 'ip_dst_addr');
+ page.unGroup();
+ });
+
+ it('should have group details for single group by', () => {
+ let dashRowValues = ['0', 'alerts_ui_e2e', 'ALERTS', '169'];
+ let row1_page1 = ['-', 'dcda4423-7...0962fafc47', '2017-09-13 17:59:32', 'alerts_ui_e2e',
+ '192.168.138.158', 'US', '72.34.49.86', 'comarksecurity.com', 'NEW', '', ''];
+ let row1_page2 = ['-', '07b29c29-9...ff19eaa888', '2017-09-13 17:59:37', 'alerts_ui_e2e',
+ '192.168.138.158', 'FR', '62.75.195.236', '62.75.195.236', 'NEW', '', ''];
+
+ page.selectGroup('source:type');
+ expect(page.getActiveGroups()).toEqualBcoz(['source:type'], 'only source type group should be selected');
+ expect(page.getDashGroupValues('alerts_ui_e2e')).toEqualBcoz(dashRowValues, 'Dash Group Values should be present');
+
+ page.expandDashGroup('alerts_ui_e2e');
+ expect(page.getDashGroupTableValuesForRow('alerts_ui_e2e', 0)).toEqualBcoz(row1_page1, 'Dash Group Values should be present');
+
+ page.clickOnNextPage('alerts_ui_e2e');
+ expect(page.getTableValuesByRowId('alerts_ui_e2e', 0, 'FR')).toEqualBcoz(row1_page2, 'Dash Group Values should be present');
+
+ page.unGroup();
+ expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');
+ });
+
+ it('should have group details for multiple group by', () => {
+
+ let dashRow_runLoveUs = {
+ 'dashRow': ['0', 'runlove.us', 'ALERTS', '13'],
+ 'firstSubGroup': '0 US (13)',
+ 'firstSubGroupIdCol': ['9a969c64-b...001cb011a3', 'a651f7c3-1...a97d4966c9', 'afc36901-3...d931231ab2',
+ 'd860ac35-1...f9e282d571', '04a5c3d0-9...af17c06fbc']
+ };
+
+ let dashRow_62_75_195_236 = {
+ 'dashRow': ['0', '62.75.195.236', 'ALERTS', '18'],
+ 'firstSubGroup': '0 FR (18)',
+ 'firstSubGroupIdCol': ['07b29c29-9...ff19eaa888', '7cd91565-1...de5be54a6e', 'ca5bde58-a...f3a88d2df4',
+ '5d6faf83-8...b88a407647', 'e2883424-f...79bb8b0606']
+ };
+
+ page.selectGroup('host');
+ page.selectGroup('enrichments:geo:ip_dst_addr:country');
+ expect(page.getActiveGroups()).toEqualBcoz(['host', 'enrichments:geo:ip_dst_addr:country'], 'two groups should be selected');
+
+ expect(page.getDashGroupValues('runlove.us')).toEqualBcoz(dashRow_runLoveUs.dashRow,
+ 'Dash Group Values should be present for runlove.us');
+ page.expandDashGroup('runlove.us');
+ expect(page.getSubGroupValues('runlove.us', 'US')).toEqualBcoz(dashRow_runLoveUs.firstSubGroup,
+ 'Dash Group Values should be present for runlove.us');
+ page.expandSubGroup('runlove.us', 'US');
+ expect(page.getCellValuesFromTable('runlove.us', 'id', '04a5c3d0-9...af17c06fbc')).toEqual(dashRow_runLoveUs.firstSubGroupIdCol,
+ 'id should not be sorted');
+
+ expect(page.getDashGroupValues('62.75.195.236')).toEqualBcoz(dashRow_62_75_195_236.dashRow, 'Dash Group Values should be present');
+ page.expandDashGroup('62.75.195.236');
+ expect(page.getSubGroupValues('62.75.195.236', 'FR')).toEqualBcoz(dashRow_62_75_195_236.firstSubGroup,
+ 'Dash Group Values should be present for 62.75.195.236');
+ page.expandSubGroup('62.75.195.236', 'FR');
+ expect(page.getCellValuesFromTable('62.75.195.236', 'id', 'e2883424-f...79bb8b0606')).toEqual(dashRow_62_75_195_236.firstSubGroupIdCol,
+ 'id should not be sorted');
+
+ page.unGroup();
+ expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');
+ });
+
+
+ it('should have sort working for group details for multiple sub groups', () => {
+
+ let usIDCol = ['dcda4423-7...0962fafc47', '9a969c64-b...001cb011a3', 'a651f7c3-1...a97d4966c9',
+ 'afc36901-3...d931231ab2', 'd860ac35-1...f9e282d571'];
+ let ruIDCol = ['350c0e9f-a...3cbe5b29d2', '9b47e24a-e...2ca6627943', '4cac5e2c-3...3deb1ebcc6',
+ 'eb54c3fa-c...e02719c3b0', 'cace11d0-c...b1bd7b9499'];
+ let frIDCol = ['07b29c29-9...ff19eaa888', '7cd91565-1...de5be54a6e', 'ca5bde58-a...f3a88d2df4',
+ '5d6faf83-8...b88a407647', 'e2883424-f...79bb8b0606'];
+
+ let usSortedIDCol = ['04a5c3d0-9...af17c06fbc', '06e70f55-4...f486927126', '105529cb-2...61b58237cc',
+ '4c732cb0-0...6a93129aba', '500eb5e2-6...37b0f98772'];
+ let ruSortedIDCol = ['001b5451-6...38ec4221ee', '00814048-d...c9e6f27800', '0454b31e-e...0a711a36e7',
+ '09552ace-9...e146579030', '0e99ba49-4...456c107bc9'];
+ let frSortedIDCol = ['07b29c29-9...ff19eaa888', '2681ed49-b...c33a80d429', '29ffaeb4-e...36822e5f81',
+ '2cc174d7-c...8073777309', '436b9ecf-b...5f1ece4c4d'];
+
+ page.selectGroup('source:type');
+ page.selectGroup('enrichments:geo:ip_dst_addr:country');
+
+ page.expandDashGroup('alerts_ui_e2e');
+ page.expandSubGroup('alerts_ui_e2e', 'US');
+ page.expandSubGroup('alerts_ui_e2e', 'RU');
+ page.expandSubGroup('alerts_ui_e2e', 'FR');
+
+ let unsortedIds = [...usIDCol, ...ruIDCol, ...frIDCol];
+ let sortedIds = [...usSortedIDCol, ...ruSortedIDCol, ...frSortedIDCol];
+
+ expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', 'e2883424-f...79bb8b0606')).toEqual(unsortedIds, 'id should not be sorted');
+
+ page.sortSubGroup('alerts_ui_e2e', 'id');
+
+ expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', '436b9ecf-b...5f1ece4c4d')).toEqual(sortedIds, 'id should be sorted');
+
+ page.unGroup();
+ expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');
+ });
+
+ it('should have search working for group details for multiple sub groups', () => {
+
+ page.selectGroup('source:type');
+ page.selectGroup('enrichments:geo:ip_dst_addr:country');
+
+ page.expandDashGroup('alerts_ui_e2e');
+ expect(page.getNumOfSubGroups('alerts_ui_e2e')).toEqual(3, 'three sub groups should be present');
+
+ listPage.setSearchText('enrichments:geo:ip_dst_addr:country:FR');
+
+ expect(page.getNumOfSubGroups('alerts_ui_e2e')).toEqual(1, 'one sub groups should be present');
+ page.expandSubGroup('alerts_ui_e2e', 'FR');
+
+ let expected = ['FR', 'FR', 'FR', 'FR', 'FR'];
+ expect(page.getCellValuesFromTable('alerts_ui_e2e', 'enrichments:geo:ip_dst_addr:country', 'FR')).toEqual(expected,
+ 'id should be sorted');
+
+ page.unGroup();
+ expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected');
+ });
+
+});
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.po.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.po.ts b/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.po.ts
new file mode 100644
index 0000000..b8472df
--- /dev/null
+++ b/metron-interface/metron-alerts/e2e/alerts-list/tree-view/tree-view.po.ts
@@ -0,0 +1,162 @@
+/**
+ * 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 {browser, element, by, protractor} from 'protractor';
+import {waitForElementPresence, waitForTextChange} from '../../utils/e2e_util';
+
+export class TreeViewPage {
+ navigateToAlertsList() {
+ browser.waitForAngularEnabled(false);
+ return browser.get('/alerts-list');
+ }
+
+ clickOnRow(id: string) {
+ let idElement = element(by.css('a[title="' + id +'"]'));
+ waitForElementPresence(idElement)
+ .then(() => browser.actions().mouseMove(idElement).perform())
+ .then(() => idElement.element(by.xpath('../..')).all(by.css('td')).get(9).click());
+ browser.sleep(2000);
+ }
+
+ getActiveGroups() {
+ return element.all(by.css('app-group-by .group-by-items.active')).getAttribute('data-name');
+ }
+
+ getGroupByCount() {
+ return waitForElementPresence(element.all(by.css('app-group-by .group-by-items'))).then(() => {
+ return element.all(by.css('app-group-by .group-by-items')).count();
+ });
+ }
+
+ getGroupByItemNames() {
+ return element.all(by.css('app-group-by .group-by-items .name')).getText();
+ }
+
+ getGroupByItemCounts() {
+ return element.all(by.css('app-group-by .group-by-items .count')).getText();
+ }
+
+ getSubGroupValues(name: string, rowName: string) {
+ return element(by.css('[data-name="' + name + '"] table tbody tr[data-name="' + rowName + '"]')).getText();
+ }
+
+ selectGroup(name: string) {
+ return element(by.css('app-group-by div[data-name="' + name + '"]')).click();
+ }
+
+ dragGroup(from: string, to: string) {
+ browser.actions().dragAndDrop(
+ element(by.css('app-group-by div[data-name="' + from + '"]')),
+ element(by.css('app-group-by div[data-name="' + to + '"]'))
+ ).perform();
+ }
+
+ getDashGroupValues(name: string) {
+ return waitForElementPresence(element(by.css('[data-name="' + name + '"] .card-header span'))).then(() => {
+ return element.all(by.css('[data-name="' + name + '"] .card-header span')).getText();
+ });
+ }
+
+ expandDashGroup(name: string) {
+ waitForElementPresence( element(by.css('[data-name="' + name + '"] .card-header'))).then(() => {
+ this.scrollToDashRow(name);
+ element(by.css('[data-name="' + name + '"] .card-header i')).click();
+ browser.sleep(2000);
+ });
+ }
+
+ expandSubGroup(groupName: string, rowName: string) {
+ browser.actions().mouseMove(element(by.css('[data-name="' + groupName + '"] tr[data-name="' + rowName + '"]'))).perform();
+ return element(by.css('[data-name="' + groupName + '"] tr[data-name="' + rowName + '"]')).click();
+ }
+
+ getDashGroupTableValuesForRow(name: string, rowId: number) {
+ this.scrollToDashRow(name);
+ return waitForElementPresence(element(by.css('[data-name="' + name + '"] table tbody tr'))).then(() => {
+ return element.all(by.css('[data-name="' + name + '"] table tbody tr')).get(rowId).all(by.css('td')).getText();
+ });
+ }
+
+ getTableValuesByRowId(name: string, rowId: number, waitForAnchor: string) {
+ return waitForElementPresence(element(by. cssContainingText('[data-name="' + name + '"] a', waitForAnchor))).then(() => {
+ return element.all(by.css('[data-name="' + name + '"] table tbody tr')).get(rowId).all(by.css('td')).getText();
+ });
+ }
+
+ getTableValuesForRow(name: string, rowName: string, waitForAnchor: string) {
+ return waitForElementPresence(element(by. cssContainingText('[data-name="' + name + '"] a', waitForAnchor))).then(() => {
+ return element.all(by.css('[data-name="' + name + '"] tr[data-name="' + rowName + '"]')).all(by.css('td')).getText();
+ });
+ }
+
+ scrollToDashRow(name: string) {
+ let scrollToEle = element(by.css('[data-name="' + name + '"] .card-header'));
+ waitForElementPresence(scrollToEle).then(() => {
+ return browser.actions().mouseMove(scrollToEle).perform();
+ });
+ }
+
+ clickOnNextPage(name: string) {
+ return element(by.css('[data-name="' + name + '"] i.fa-chevron-right')).click();
+ }
+
+ unGroup() {
+ return element(by.css('app-group-by .ungroup-button')).click();
+ }
+
+ getIdOfAllExpandedRows() {
+ return element.all(by.css('[data-name="' + name + '"] table tbody tr')).then(row => {
+ browser.pause();
+ });
+ }
+
+ getNumOfSubGroups(groupName: string) {
+ return element.all(by.css('[data-name="' + groupName + '"] table tbody tr')).count();
+ }
+
+ getCellValuesFromTable(groupName: string, cellName: string, waitForAnchor: string) {
+ return waitForElementPresence(element(by. cssContainingText('[data-name="' + cellName + '"] a', waitForAnchor))).then(() => {
+ return element.all(by.css('[data-name="' + groupName + '"] table tbody [data-name="' + cellName + '"]')).map(element => {
+ browser.actions().mouseMove(element).perform();
+ return (element.getText());
+ });
+ });
+ }
+
+ sortSubGroup(groupName: string, colName: string) {
+ return element.all(by.css('[data-name="' + groupName + '"] metron-config-sorter[title="' + colName + '"]')).click();
+ }
+
+ toggleAlertInTree(index: number) {
+ let selector = by.css('app-tree-view tbody tr');
+ let checkbox = element.all(selector).get(index).element(by.css('label'));
+ waitForElementPresence(checkbox).then(() => {
+ browser.actions().mouseMove(checkbox).perform().then(() => {
+ checkbox.click();
+ });
+ });
+ }
+
+ getAlertStatusForTreeView(rowIndex: number, previousText) {
+ let row = element.all(by.css('app-tree-view tbody tr')).get(rowIndex);
+ let column = row.all(by.css('td a')).get(8);
+ return waitForTextChange(column, previousText).then(() => {
+ return column.getText();
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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 2f0f81d..8d37800 100644
--- a/metron-interface/metron-alerts/e2e/login/login.po.ts
+++ b/metron-interface/metron-alerts/e2e/login/login.po.ts
@@ -51,6 +51,7 @@ export class LoginPage {
browser.waitForAngularEnabled(false);
let errElement = element(by.css('.login-failed-msg'));
return waitForElementVisibility(errElement).then(() => {
+ browser.sleep(1000);
return errElement.getText().then((message) => {
return message.replace(/\n/, '').replace(/LOG\ IN$/, '');
});
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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 341e668..47f01e2 100644
--- a/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
+++ b/metron-interface/metron-alerts/e2e/utils/e2e_util.ts
@@ -17,7 +17,12 @@ export function waitForURL(url: string) {
export function waitForText(element, text) {
let EC = protractor.ExpectedConditions;
- return browser.wait(EC.textToBePresentInElement(element, text));
+ return browser.wait(EC.textToBePresentInElementValue(element, text));
+}
+
+export function waitForTextChange(element, previousText) {
+ let EC = protractor.ExpectedConditions;
+ return browser.wait(EC.not(EC.textToBePresentInElement(element, previousText)));
}
export function waitForElementInVisibility (_element ) {
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/package.json
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/package.json b/metron-interface/metron-alerts/package.json
index 6ff3c3c..1322a65 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",
+ "ng2-dragula": "^1.5.0",
"moment": "^2.18.1",
"rxjs": "^5.1.0",
"web-animations-js": "^2.2.2",
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/protractor.conf.js
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/protractor.conf.js b/metron-interface/metron-alerts/protractor.conf.js
index fe0fec0..4fc25be 100644
--- a/metron-interface/metron-alerts/protractor.conf.js
+++ b/metron-interface/metron-alerts/protractor.conf.js
@@ -29,6 +29,7 @@ exports.config = {
'./e2e/alerts-list/alerts-list.e2e-spec.ts',
'./e2e/alerts-list/configure-table/configure-table.e2e-spec.ts',
'./e2e/alerts-list/save-search/save-search.e2e-spec.ts',
+ './e2e/alerts-list/tree-view/tree-view.e2e-spec.ts',
'./e2e/alerts-list/alert-filters/alert-filters.e2e-spec.ts',
'./e2e/alerts-list/alert-status/alerts-list-status.e2e-spec.ts',
'./e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts'
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/_hexagon.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/_hexagon.scss b/metron-interface/metron-alerts/src/_hexagon.scss
new file mode 100644
index 0000000..667008a
--- /dev/null
+++ b/metron-interface/metron-alerts/src/_hexagon.scss
@@ -0,0 +1,91 @@
+/**
+ * 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";
+
+.hexagon {
+ position: absolute;
+ left: -15px;
+ top: 17px;
+ width: 30px;
+ height: 17.321px;
+ background-color: #64C7CC;
+ margin: 8.660px 0;
+
+ &:before,
+ &:after {
+ content: "";
+ position: absolute;
+ width: 0;
+ border-left: 15px solid transparent;
+ border-right: 15px solid transparent;
+ }
+
+ &:before {
+ bottom: 100%;
+ border-bottom: 8.660px solid #64C7CC;
+ }
+
+ &:after {
+ top: 100%;
+ width: 0;
+ border-top: 8.660px solid #64C7CC;
+ }
+
+ &.error {
+ background-color: $errors-red;
+ &:before {
+ border-bottom: 8.660px solid $errors-red;
+ }
+
+ &:after {
+ border-top: 8.660px solid $errors-red;
+ }
+ }
+
+ &.warning {
+ background-color: $warning-yellow;
+ &:before {
+ border-bottom: 8.660px solid $warning-yellow;
+ }
+
+ &:after {
+ border-top: 8.660px solid $warning-yellow;
+ }
+ }
+
+ &.info {
+ background-color: $info-yellow;
+ &:before {
+ border-bottom: 8.660px solid $info-yellow;
+ }
+
+ &:after {
+ border-top: 8.660px solid $info-yellow;
+ }
+ }
+
+ span {
+ line-height: 1.7;
+ position: absolute;
+ font-size: 12px;
+ text-align: center;
+ width: 100%;
+ color: $white;
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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 e4055ce..44ed9f6 100644
--- a/metron-interface/metron-alerts/src/_variables.scss
+++ b/metron-interface/metron-alerts/src/_variables.scss
@@ -32,6 +32,10 @@ $list-group-border-color: #404040;
$list-group-active-bg: #28A9E2;
$list-group-item-padding-y: 4px;
$list-group-item-padding-x: 10px;
+$tooltip-bg: #494411;
+$tooltip-padding-y: 2px;
+$tooltip-padding-x: 5px;
+$tooltip-arrow-width: 0px;
//Metron variables
$mine-shaft: #2E2E2E;
@@ -40,26 +44,36 @@ $mine-shaft-2: #333333;
$mine-shaft-3: #262626;
$mine-shaft-4: #383838;
$mine-shaft-5: #3D3D3D;
+$mine-shaft-6: #252525;
+$mine-shaft-7: #2C2C2C;
+$mine-shaft-8: #353535;
+$mine-shaft-9: #2B2B2B;
+$mine-shaft-10: #303030;
$dove-grey: #737373;
$tundora: #4D4D4D;
$tundora-1: #404040;
+$tundora-2: #4C4C4C;
$curious-blue: #27AAE1;
$blue-chill: #0F6F9E;
$piction-blue: #32ABDF;
+$picton-blue-1: #53C3EA;
$dove-grey: #6B6B6B;
$dove-grey-1: #676767;
+$dove-grey-2: #6D6D6D;
$checkbox-checked-color: #32abe2;
$edit-panel-background: #083b44;
$errors-red: #D60A15;
$warning-yellow: #D6711D;
$info-yellow: #AC9B5A;
$eden: #0C3B43;
+$eden-1: #0F4450;
$all-ports: #006EA0;
$blue-mine: #1B596C;
$gothic: #689AA9;
$tiber: #0B363E;
$silver: #BDBDBD;
$silver-1: #B8B8B8;
+$silver-2: #C8C8C8;
$breaker-bay: #669AAA;
$gray: #909090;
$silver-chalice: #B2B2B2;
@@ -72,6 +86,8 @@ $eastern-blue: #1F91BE;
$mantis: #80BF4D;
$sky-blue: #75D2ED;
$outer-space: #2E3A3F;
+$white: #FFFFFF;
+$iron: #D1D3D4;
$rolling-stone: #808285;
$nile-blue: #18404E;
$apple-blossom: #A94442;
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html
index 1183223..bcecef3 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.html
@@ -40,6 +40,9 @@
</div>
<div class="col-md-3 px-0">
<div class="pull-right" style="position: relative; display: block;">
+ <div class="btn cog">
+ <i class="fa fa-cog configure-table-icon" (click)="showConfigureTable()"></i>
+ </div>
<div class="btn settings">
<i #settingsIcon class="fa fa-sliders" aria-hidden="true"></i>
</div>
@@ -64,25 +67,36 @@
<div class="container-fluid no-gutters">
<div class="row">
- <div class="px-0" style="width: 200px;max-width: 200px;">
- <app-alert-filters [facets]="searchResponse.facetCounts" (facetFilterChange)="onAddFacetFilter($event)"> </app-alert-filters>
+ <div class="px-0" style="width: 200px;max-width: 200px;">
+ <app-alert-filters [facets]="searchResponse.facetCounts" (facetFilterChange)="onAddFacetFilter($event)"> </app-alert-filters>
+ </div>
+ <div class="col px-0 ml-4">
+ <div class="col-sm-12 pl-0 pb-3">
+ <app-group-by [facets]="searchResponse.facetCounts" (groupsChange)="onGroupsChange($event)"> </app-group-by>
</div>
- <div class="col">
+ <div class="col-sm-12 px-0">
<app-table-view #dataViewComponent
- [alerts]="alerts"
+ [alerts]="alerts" *ngIf="queryBuilder.groupRequest.groups.length === 0"
[queryBuilder]="queryBuilder"
[pagination]="pagination"
[alertsColumnsToDisplay]="alertsColumnsToDisplay"
- [(selectedAlerts)]="selectedAlerts"
+ [selectedAlerts]="selectedAlerts"
(onResize)="onResize()"
(onAddFilter)="onAddFilter($event)"
(onRefreshData)="onRefreshData($event)"
(onShowDetails)="showDetails($event)"
- (onShowConfigureTable)="showConfigureTable()"
- (onSelectedAlertsChange)="onSelectedAlertsChange($event)">
-
- </app-table-view>
+ (onSelectedAlertsChange)="onSelectedAlertsChange($event)"></app-table-view>
+ <app-tree-view #dataViewComponent *ngIf="queryBuilder.groupRequest.groups.length !== 0"
+ [alerts]="alerts"
+ [queryBuilder]="queryBuilder"
+ [alertsColumnsToDisplay]="alertsColumnsToDisplay"
+ [selectedAlerts]="selectedAlerts"
+ (onResize)="onResize()"
+ (onAddFilter)="onAddFilter($event)"
+ (onShowDetails)="showDetails($event)"
+ (onSelectedAlertsChange)="onSelectedAlertsChange($event)"></app-tree-view>
</div>
</div>
+ </div>
</div>
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss
index 6a26d3c..a803df0 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.scss
@@ -205,7 +205,7 @@ $searchbox-height: 42px;
}
}
-.settings {
+.settings, .cog {
height: 38px;
padding: 0px;
line-height: 40px;
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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 46b7796..06d3fb2 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
@@ -38,7 +38,6 @@ import {ElasticsearchUtils} from '../../utils/elasticsearch-utils';
import {TableViewComponent} from './table-view/table-view.component';
import {Filter} from '../../model/filter';
import {Pagination} from '../../model/pagination';
-import {environment} from '../../../environments/environment';
import {PatchRequest} from '../../model/patch-request';
@Component({
@@ -60,7 +59,6 @@ export class AlertsListComponent implements OnInit, OnDestroy {
pauseRefresh = false;
lastPauseRefreshValue = false;
threatScoreFieldName = 'threat:triage:score';
- indices: string[];
@ViewChild('table') table: ElementRef;
@ViewChild('dataViewComponent') dataViewComponent: TableViewComponent;
@@ -85,9 +83,6 @@ export class AlertsListComponent implements OnInit, OnDestroy {
this.restoreRefreshState();
}
});
- if (environment.indices) {
- this.indices = environment.indices.split(',');
- }
}
addAlertChangedListner() {
@@ -107,6 +102,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
addLoadSavedSearchListner() {
this.saveSearchService.loadSavedSearch$.subscribe((savedSearch: SaveSearch) => {
let queryBuilder = new QueryBuilder();
+ queryBuilder.setGroupby(this.queryBuilder.groupRequest.groups.map(group => group.field));
queryBuilder.searchRequest = savedSearch.searchRequest;
this.queryBuilder = queryBuilder;
this.prepareColumnData(savedSearch.tableColumns, []);
@@ -115,7 +111,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
}
calcColumnsToDisplay() {
- let availableWidth = document.documentElement.clientWidth - (200 + (15 * 3)); /* screenwidth - (navPaneWidth + (paddings))*/
+ let availableWidth = document.documentElement.clientWidth - (200 + (15 * 4)); /* screenwidth - (navPaneWidth + (paddings))*/
availableWidth = availableWidth - (55 + 25 + 25); /* availableWidth - (score + colunSelectIcon +selectCheckbox )*/
let tWidth = 0;
this.alertsColumnsToDisplay = this.alertsColumns.filter(colMetaData => {
@@ -181,6 +177,7 @@ export class AlertsListComponent implements OnInit, OnDestroy {
}
onSelectedAlertsChange(selectedAlerts) {
+ this.selectedAlerts = selectedAlerts;
if (selectedAlerts.length > 0) {
this.pause();
} else {
@@ -198,6 +195,11 @@ export class AlertsListComponent implements OnInit, OnDestroy {
this.search();
}
+ onGroupsChange(groups) {
+ this.queryBuilder.setGroupby(groups);
+ this.search();
+ }
+
onPausePlay() {
this.pauseRefresh = !this.pauseRefresh;
if (this.pauseRefresh) {
@@ -232,8 +234,6 @@ export class AlertsListComponent implements OnInit, OnDestroy {
this.updateService.updateAlertState(this.selectedAlerts, 'ESCALATE').subscribe(results => {
this.updateSelectedAlertStatus('ESCALATE');
});
- this.alertsService.escalate(this.selectedAlerts).subscribe();
-
}
processDismiss() {
@@ -269,14 +269,9 @@ export class AlertsListComponent implements OnInit, OnDestroy {
if (resetPaginationParams) {
this.pagination.from = 0;
}
- this.queryBuilder.searchRequest.from = this.pagination.from;
- if (this.tableMetaData.size) {
- this.pagination.size = this.tableMetaData.size;
- }
- this.queryBuilder.searchRequest.size = this.pagination.size;
- if (this.indices) {
- this.queryBuilder.searchRequest.indices = this.indices;
- }
+
+ this.setSearchRequestSize();
+
this.searchService.search(this.queryBuilder.searchRequest).subscribe(results => {
this.setData(results);
}, error => {
@@ -287,6 +282,19 @@ export class AlertsListComponent implements OnInit, OnDestroy {
this.tryStartPolling();
}
+ setSearchRequestSize() {
+ if (this.queryBuilder.groupRequest.groups.length == 0) {
+ this.queryBuilder.searchRequest.from = this.pagination.from;
+ if (this.tableMetaData.size) {
+ this.pagination.size = this.tableMetaData.size;
+ }
+ this.queryBuilder.searchRequest.size = this.pagination.size;
+ } else {
+ this.queryBuilder.searchRequest.from = 0;
+ this.queryBuilder.searchRequest.size = 0;
+ }
+ }
+
saveCurrentSearch(savedSearch: SaveSearch) {
if (this.queryBuilder.query !== '*') {
if (!savedSearch) {
@@ -375,9 +383,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)
- .map(alert => alert.source['alert_status'] = status);
+ selectedAlert.source['alert_status'] = status;
}
this.selectedAlerts = [];
this.resume();
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts
index 8ee194e..27b7e2e 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.module.ts
@@ -16,6 +16,7 @@
* limitations under the License.
*/
import {NgModule} from '@angular/core';
+import {DecimalPipe} from '@angular/common';
import {AlertsListComponent} from './alerts-list.component';
import {routing} from './alerts-list.routing';
@@ -26,15 +27,17 @@ import {ListGroupModule} from '../../shared/list-group/list-grup.module';
import {CollapseModule} from '../../shared/collapse/collapse.module';
import {MetronTablePaginationModule} from '../../shared/metron-table/metron-table-pagination/metron-table-pagination.module';
import {ConfigureRowsModule} from '../configure-rows/configure-rows.module';
+import {GroupByModule} from '../../shared/group-by/group-by.module';
import {AlertFiltersComponent} from './alert-filters/alert-filters.component';
import {TableViewComponent} from './table-view/table-view.component';
+import {TreeViewComponent} from './tree-view/tree-view.component';
@NgModule({
imports: [routing, SharedModule, ConfigureRowsModule, MetronSorterModule, MetronTablePaginationModule,
- ListGroupModule, CollapseModule],
+ ListGroupModule, CollapseModule, GroupByModule],
exports: [AlertsListComponent],
- declarations: [AlertsListComponent, TableViewComponent, AlertFiltersComponent],
- providers: [SearchService]
+ declarations: [AlertsListComponent, TableViewComponent, TreeViewComponent, AlertFiltersComponent],
+ providers: [DecimalPipe, SearchService]
})
export class AlertsListModule {
}
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts
index 0b76ee1..863e127 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/query-builder.ts
@@ -19,9 +19,12 @@ import {Filter} from '../../model/filter';
import {ColumnNamesService} from '../../service/column-names.service';
import {SearchRequest} from '../../model/search-request';
import {SortField} from '../../model/sort-field';
+import {GroupRequest} from '../../model/group-request';
+import {Group} from '../../model/group';
export class QueryBuilder {
private _searchRequest = new SearchRequest();
+ private _groupRequest = new GroupRequest();
private _query = '*';
private _displayQuery = this._query;
private _filters: Filter[] = [];
@@ -62,6 +65,11 @@ export class QueryBuilder {
this.query = this._searchRequest.query;
}
+ get groupRequest(): GroupRequest {
+ this._groupRequest.query = this.generateSelect();
+ return this._groupRequest;
+ }
+
addOrUpdateFilter(filter: Filter) {
let existingFilter = this._filters.find(tFilter => tFilter.field === filter.field);
if (existingFilter) {
@@ -111,6 +119,10 @@ export class QueryBuilder {
this.searchRequest.size = size;
}
+ setGroupby(groups: string[]) {
+ this.groupRequest.groups = groups.map(groupName => new Group(groupName));
+ }
+
setSort(sortBy: string, order: string) {
let sortField = new SortField();
sortField.field = sortBy;
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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 561b299..b8fd14f 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
@@ -18,7 +18,6 @@
<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>
</thead>
@@ -31,7 +30,6 @@
<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> <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/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss
index fa2417e..eec7f92 100644
--- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.scss
@@ -18,7 +18,7 @@
@import "../../../../variables.scss";
.table-wrapper {
- min-height: calc(100vh - 250px);
+ min-height: calc(100vh - 320px);
}
.configure-table-icon {
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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
new file mode 100644
index 0000000..ae65a67
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-group-data.ts
@@ -0,0 +1,65 @@
+/**
+ * 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 {Subscription} from 'rxjs/Rx';
+import {SearchResponse} from '../../../model/search-response';
+import {Pagination} from '../../../model/pagination';
+import {TREE_SUB_GROUP_SIZE} from '../../../utils/constants';
+import {SortField} from '../../../model/sort-field';
+import {SortEvent} from '../../../shared/metron-table/metron-table.directive';
+import {Sort} from '../../../utils/enums';
+
+export class TreeGroupData {
+ key: string;
+ total: number;
+ level: number;
+ show: boolean;
+ expand = false;
+ score: number;
+
+ // Used by only Dashrow
+ sortField: SortField;
+ sortEvent: SortEvent = { sortBy: '', type: '', sortOrder: Sort.ASC};
+ treeSubGroups: TreeGroupData[] = [];
+
+ // Used by only Leafnodes
+ groupQueryMap = null;
+ response: SearchResponse = new SearchResponse();
+ pagingData: Pagination = new Pagination();
+
+
+ constructor(key: string, total: number, score: number, level: number, expand: boolean) {
+ this.key = key;
+ this.total = total;
+ this.score = score;
+ this.level = level;
+ this.show = expand;
+
+ this.pagingData.size = TREE_SUB_GROUP_SIZE;
+ }
+}
+
+
+export class TreeAlertsSubscription {
+ refreshTimer: Subscription;
+ group: TreeGroupData;
+
+ constructor(refreshTimer: Subscription, group: TreeGroupData) {
+ this.refreshTimer = refreshTimer;
+ this.group = group;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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
new file mode 100644
index 0000000..e89cdc9
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.html
@@ -0,0 +1,114 @@
+<!--
+ 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 role="tablist" class="tree-wrapper" aria-multiselectable="true">
+ <div class="card" *ngFor="let group of topGroups; let i = index;" [attr.data-name]="group.key">
+ <div class="card-header" role="tab" [attr.id]="'title-' + i">
+ <div class="hexagon" appAlertSeverityHexagon [severity]="group.score">
+ <span *ngIf="group.score < 1000" class="dash-score">{{ group.score }}</span>
+ <span *ngIf="group.score >= 1000" class="dash-score">999<sup>+</sup></span>
+ </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-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>
+ </div>
+ </div>
+ </div>
+ <div class="collapse" role="tabpanel" [ngClass]="{'show': group.expand}">
+ <div class="card-block">
+ <div class="table-wrapper">
+ <table class="table table-sm" metron-config-table [data]="alerts" [cellSelectable]="true" (onSort)="sortTreeSubGroup($event, group)" style="white-space: nowrap;" (window:resize)="resize()" #table>
+ <thead>
+ <tr>
+ <th> </th>
+ <th class="table-score-col">
+ <metron-config-sorter [type]="'number'" [sortBy]="threatScoreFieldName" [sortOnCol]="group.sortEvent.sortBy" [sortOrder]="group.sortEvent.sortOrder"> 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}}" [sortOnCol]="group.sortEvent.sortBy" [sortOrder]="group.sortEvent.sortOrder"> {{ column.name | columnNameTranslate | centerEllipses:15 }}</metron-config-sorter>
+ </th>
+ <th style="width: 15px"></th>
+ <th style="width:25px"><input id="select-deselect-all-{{ group.key }}" class="fontawesome-checkbox" type="checkbox" (click)="selectAllGroupRows($event, group)">
+ <label for="select-deselect-all-{{ group.key }}"></label>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <ng-container>
+ <tr *ngFor="let alert of group.response.results" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1, 'd-none': !group.expand || !group.show}" (click)="showDetails($event, alert)">
+ <td [attr.colspan]="2" (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])">
+ <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]">
+ <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }}</a>
+ </div>
+ </td>
+ <td #cell *ngFor="let column of alertsColumnsToDisplay" [attr.data-name]="column.name">
+ <a title="{{getValue(alert, column, true)}}" style="color:#689AA9" (click)="addFilter(column.name, getValue(alert, column, false))">{{ getValue(alert, column, true) | centerEllipses:20:cell }}</a>
+ </td>
+ <td> <i class="fa fa-comments-o" aria-hidden="true" *ngIf="alert.source.comments && alert.source.comments.length > 0"></i> </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>
+
+ <tr *ngIf="group.response.results.length > 0" class="no-hover" [ngClass]="{'d-none': !group.expand || !group.show}">
+ <td [attr.colspan]="alertsColumnsToDisplay.length + 3" class="text-right">
+ <metron-table-pagination [(pagination)]="group.pagingData" (pageChange)="groupPageChange(group)"> </metron-table-pagination>
+ </td>
+ </tr>
+ </ng-container>
+ <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))}">
+ <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>
+ </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)">
+ <td [attr.colspan]="2" [ngStyle]="{'padding-left.px': (16 * (subGroup.level -1)) + 23}" (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])">
+ <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]">
+ <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }}</a>
+ </div>
+ </td>
+ <td #cell *ngFor="let column of alertsColumnsToDisplay" [attr.data-name]="column.name">
+ <a (click)="addFilter(column.name, getValue(alert, column, false))" title="{{getValue(alert, column, true)}}" style="color:#689AA9" (click)="addFilter(column.name, getValue(alert, column, false))">{{ getValue(alert, column, true) | centerEllipses:20:cell }}</a>
+ </td>
+ <td> <i class="fa fa-comments-o" aria-hidden="true" *ngIf="alert.source.comments && alert.source.comments.length > 0"></i> </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>
+
+ <tr *ngIf="subGroup.response.results.length > 0" class="no-hover" [ngClass]="{'d-none': !subGroup.expand || !subGroup.show}">
+ <td [attr.colspan]="alertsColumnsToDisplay.length + 3" class="text-right">
+ <metron-table-pagination [(pagination)]="subGroup.pagingData" (pageChange)="groupPageChange(subGroup)"> </metron-table-pagination>
+ </td>
+ </tr>
+
+ </ng-container>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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
new file mode 100644
index 0000000..8668b49
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.scss
@@ -0,0 +1,153 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@import "../../../../variables.scss";
+
+$group-height: 70px;
+
+.card {
+ background: none;
+ border-radius: 0px;
+ margin-bottom: 12px;
+
+ .title {
+ font-size: 20px;
+ }
+
+ .group-value {
+ padding-left: 12px;
+ }
+
+ .severity-padding {
+ padding-left: 30px;
+ }
+
+ .text-light {
+ font-family: Roboto;
+ color: $iron;
+ }
+
+ .text-dark {
+ font-size: 12px;
+ font-family: Roboto;
+ color: $dusty-grey;
+ }
+
+ .card-header {
+ padding: 0px;
+ border-radius: 0px;
+ height: $group-height;
+ line-height: $group-height;
+ background: $mine-shaft-8;
+ }
+
+ .down-arrow {
+ padding: 10px 20px;
+
+ &:hover {
+ background: $eden-1;
+ border: 2px solid $blue-mine;
+ }
+
+ &:after {
+ top: 2px;
+ right: 37px;
+ color: $gothic;
+ font-size: 25px;
+ content: '\f107';
+ font-style: normal;
+ position: absolute;
+ font-family: "FontAwesome";
+ }
+ }
+
+ .two-line {
+ padding-top: 16px;
+ line-height: 1;
+ }
+
+ .collapse {
+ background: $mine-shaft-9;
+ }
+ .card-block {
+ padding-bottom: 0px;
+ background: $mine-shaft-9;
+ }
+
+ .fa-caret-right, .fa-caret-down {
+ padding-right: 4px;
+ font-size: 15px;
+ }
+
+ .table-group-icon-col {
+ width:5px;
+ box-sizing: border-box;
+ padding: 6px 0px 7px 5px;
+ border: 1px solid transparent;
+
+ &:hover {
+ background: $eden-1;
+ border: 1px solid $blue-mine;
+ }
+ }
+
+ .table-score-col {
+ width:55px;
+ }
+
+ .table-group-row {
+ background: $mine-shaft-10;
+ }
+
+ .no-hover {
+ height: $group-height;
+
+ td {
+ text-align:center;
+ vertical-align:middle;
+ border-bottom: none;
+
+ &:hover {
+ background: none;
+ }
+ }
+ }
+}
+
+.table {
+ tr {
+ line-height: 25px;
+ height: 35px;
+ }
+}
+
+sup {
+ font-size: 100%;
+}
+
+.configure-table-icon {
+ font-size: 16px;
+ cursor: pointer;
+}
+
+.tree-wrapper {
+ min-height: calc(100vh - 280px);
+}
+
+.top-group {
+ cursor: pointer;
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts
new file mode 100644
index 0000000..8a50404
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TreeViewComponent } from './tree-view.component';
+
+describe('TreeViewComponent', () => {
+ let component: TreeViewComponent;
+ let fixture: ComponentFixture<TreeViewComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ TreeViewComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TreeViewComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should be created', () => {
+ expect(component).toBeTruthy();
+ });
+});
http://git-wip-us.apache.org/repos/asf/metron/blob/942aaaf2/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
new file mode 100644
index 0000000..75a7e1c
--- /dev/null
+++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/tree-view/tree-view.component.ts
@@ -0,0 +1,352 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import {Router} from '@angular/router';
+import {Subscription} from 'rxjs/Rx';
+
+import {TableViewComponent} from '../table-view/table-view.component';
+import {SearchResponse} from '../../../model/search-response';
+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 {UpdateService} from '../../../service/update.service';
+import {PatchRequest} from '../../../model/patch-request';
+
+@Component({
+ selector: 'app-tree-view',
+ templateUrl: './tree-view.component.html',
+ styleUrls: ['./tree-view.component.scss']
+})
+
+export class TreeViewComponent extends TableViewComponent implements OnChanges {
+
+ groupByFields: string[] = [];
+ topGroups: TreeGroupData[] = [];
+ groupResponse: GroupResponse = new GroupResponse();
+ treeGroupSubscriptionMap: {[key: string]: TreeAlertsSubscription } = {};
+
+ constructor(router: Router,
+ searchService: SearchService,
+ metronDialogBox: MetronDialogBox,
+ private updateService: UpdateService) {
+ super(router, searchService, metronDialogBox);
+ }
+
+ addAlertChangedListner() {
+ this.updateService.alertChanged$.subscribe(patchRequest => {
+ this.updateAlert(patchRequest);
+ });
+ }
+
+ collapseGroup(groupArray: TreeGroupData[], level: number, index: number) {
+ for (let i = index + 1; i < groupArray.length; i++) {
+ if (groupArray[i].level > (level)) {
+ groupArray[i].show = false;
+ groupArray[i].expand = false;
+ } else {
+ break;
+ }
+ }
+ }
+
+ createQuery(selectedGroup: TreeGroupData) {
+ let searchQuery = this.queryBuilder.generateSelect();
+ let groupQery = Object.keys(selectedGroup.groupQueryMap).map(key => {
+ return key.replace(/:/g, '\\:') +
+ ':' +
+ String(selectedGroup.groupQueryMap[key])
+ .replace(/[\*\+\-=~><\"\?^\${}\(\)\:\!\/[\]\\\s]/g, '\\$&') // replace single special characters
+ .replace(/\|\|/g, '\\||') // replace ||
+ .replace(/\&\&/g, '\\&&'); // replace &&
+ }).join(' AND ');
+
+ groupQery += searchQuery === '*' ? '' : (' AND ' + searchQuery);
+ return groupQery;
+ }
+
+ expandGroup(groupArray: TreeGroupData[], level: number, index: number) {
+ for (let i = index + 1; i < groupArray.length; i++) {
+ if (groupArray[i].level === (level + 1)) {
+ groupArray[i].show = true;
+ } else {
+ break;
+ }
+ }
+ }
+
+ getAlerts(selectedGroup: TreeGroupData): Subscription {
+ let searchRequest = new SearchRequest();
+ searchRequest.query = this.createQuery(selectedGroup);
+ searchRequest.from = selectedGroup.pagingData.from;
+ searchRequest.size = selectedGroup.pagingData.size;
+ searchRequest.sort = selectedGroup.sortField ? [selectedGroup.sortField] : [];
+
+ return this.searchGroup(selectedGroup, searchRequest);
+ }
+
+ getGroups() {
+ let groupRequest = this.queryBuilder.groupRequest;
+ groupRequest.query = this.queryBuilder.generateSelect();
+
+ this.searchService.groups(groupRequest).subscribe(groupResponse => {
+ this.updateGroupData(groupResponse);
+ });
+ }
+
+ updateGroupData(groupResponse) {
+ this.selectedAlerts = [];
+ this.groupResponse = groupResponse;
+ this.parseTopLevelGroup();
+ }
+
+ groupPageChange(group: TreeGroupData) {
+ this.getAlerts(group);
+ }
+
+ createTopGroups(groupByFields: string[]) {
+ this.topGroups = [];
+ this.treeGroupSubscriptionMap = {};
+
+ this.groupResponse.groupResults.forEach((groupResult: GroupResult) => {
+ let treeGroupData = new TreeGroupData(groupResult.key, groupResult.total, groupResult.score, 0, false);
+ if (groupByFields.length === 1) {
+ treeGroupData.groupQueryMap = this.createTopGroupQueryMap(groupByFields[0], groupResult);
+ }
+
+ this.topGroups.push(treeGroupData);
+ });
+ }
+
+ createTopGroupQueryMap(groupByFields: string, groupResult: GroupResult) {
+ let groupQueryMap = {};
+ groupQueryMap[groupByFields] = groupResult.key;
+ return groupQueryMap;
+ }
+
+ initTopGroups() {
+ let groupByFields = this.queryBuilder.groupRequest.groups.map(group => group.field);
+ let currentTopGroupKeys = this.groupResponse.groupResults.map(groupResult => groupResult.key);
+ let previousTopGroupKeys = this.topGroups.map(group => group.key);
+
+ if (this.topGroups.length === 0 || JSON.stringify(this.groupByFields) !== JSON.stringify(groupByFields) ||
+ JSON.stringify(currentTopGroupKeys) !== JSON.stringify(previousTopGroupKeys)) {
+ this.createTopGroups(groupByFields);
+ }
+
+ this.groupByFields = groupByFields;
+ }
+
+ search(resetPaginationParams = true, pageSize: number = null) {
+ this.getGroups();
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if ((changes['alerts'] && changes['alerts'].currentValue)) {
+ this.search();
+ }
+ }
+
+ ngOnInit() {
+ this.addAlertChangedListner();
+ }
+
+ searchGroup(selectedGroup: TreeGroupData, searchRequest: SearchRequest): Subscription {
+ return this.searchService.search(searchRequest).subscribe(results => {
+ this.setData(selectedGroup, results);
+ }, error => {
+ this.metronDialogBox.showConfirmationMessage(ElasticsearchUtils.extractESErrorMessage(error), DialogType.Error);
+ });
+ }
+
+ setData(selectedGroup: TreeGroupData, results: SearchResponse) {
+ selectedGroup.response.results = results.results;
+ selectedGroup.pagingData.total = results.total;
+ selectedGroup.total = results.total;
+
+ this.topGroups.map(topGroup => {
+ if (topGroup.treeSubGroups.length > 0) {
+ topGroup.total = topGroup.treeSubGroups.reduce((total, subGroup) => { return total + subGroup.total }, 0);
+ }
+ });
+ }
+
+ checkAndToSubscription(group: TreeGroupData) {
+ if (group.groupQueryMap) {
+ let key = JSON.stringify(group.groupQueryMap);
+ if (this.treeGroupSubscriptionMap[key]) {
+ this.removeFromSubscription(group);
+ }
+
+ let subscription = this.getAlerts(group);
+ this.treeGroupSubscriptionMap[key] = new TreeAlertsSubscription(subscription, group);
+ }
+ }
+
+ removeFromSubscription(group: TreeGroupData) {
+ if (group.groupQueryMap) {
+ let key = JSON.stringify(group.groupQueryMap);
+ let subscription = this.treeGroupSubscriptionMap[key].refreshTimer;
+ if (subscription && !subscription.closed) {
+ subscription.unsubscribe();
+ }
+ delete this.treeGroupSubscriptionMap[key];
+ }
+ }
+
+ toggleSubGroups(topLevelGroup: TreeGroupData, selectedGroup: TreeGroupData, index: number) {
+ selectedGroup.expand = !selectedGroup.expand;
+
+ if (selectedGroup.expand) {
+ this.expandGroup(topLevelGroup.treeSubGroups, selectedGroup.level, index);
+ this.checkAndToSubscription(selectedGroup);
+ } else {
+ this.collapseGroup(topLevelGroup.treeSubGroups, selectedGroup.level, index);
+ this.removeFromSubscription(selectedGroup);
+ }
+ }
+
+ toggleTopLevelGroup(group: TreeGroupData) {
+ group.expand = !group.expand;
+ group.show = !group.show;
+
+ if (group.expand) {
+ this.checkAndToSubscription(group);
+ } else {
+ this.removeFromSubscription(group);
+ }
+ }
+
+ parseSubGroups(group: GroupResult, groupAsArray: TreeGroupData[],
+ groupQueryMap: {[key: string]: string}, groupedBy: string, level: number, index: number): number {
+ index++;
+ groupQueryMap[groupedBy] = group.key;
+
+ let currentTreeNodeData = (groupAsArray.length > 0) ? groupAsArray[index] : null;
+
+ if (currentTreeNodeData && (currentTreeNodeData.key === group.key) && (currentTreeNodeData.level === level)) {
+ currentTreeNodeData.total = group.total;
+ } else {
+ let newTreeNodeData = new TreeGroupData(group.key, group.total, group.score, level, level === 1);
+ if (!currentTreeNodeData) {
+ groupAsArray.push(newTreeNodeData);
+ } else {
+ groupAsArray.splice(index, 1, newTreeNodeData);
+ }
+ }
+
+ if (!group.groupResults) {
+ groupAsArray[index].groupQueryMap = JSON.parse(JSON.stringify(groupQueryMap));
+ if (groupAsArray[index].expand && groupAsArray[index].show && groupAsArray[index].groupQueryMap) {
+ this.checkAndToSubscription(groupAsArray[index]);
+ }
+ return index;
+ }
+
+ group.groupResults.forEach(subGroup => {
+ index = this.parseSubGroups(subGroup, groupAsArray, groupQueryMap, group.groupedBy, level + 1, index);
+ });
+
+ return index;
+ }
+
+ parseTopLevelGroup() {
+ let groupedBy = this.groupResponse.groupedBy;
+
+ this.initTopGroups();
+
+ for (let i = 0; i < this.groupResponse.groupResults.length; i++) {
+ let index = -1;
+ let topGroup = this.topGroups[i];
+ let resultGroup = this.groupResponse.groupResults[i];
+ let groupQueryMap = this.createTopGroupQueryMap(groupedBy, resultGroup);
+
+ topGroup.total = resultGroup.total;
+
+ if (resultGroup.groupResults) {
+ resultGroup.groupResults.forEach(subGroup => {
+ index = this.parseSubGroups(subGroup, topGroup.treeSubGroups, groupQueryMap, resultGroup.groupedBy, 1, index);
+ });
+
+ topGroup.treeSubGroups.splice(index + 1);
+ }
+ }
+
+ if (this.groupByFields.length === 1) {
+ this.refreshAllExpandedGroups();
+ }
+ }
+
+ 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';
+
+ treeGroup.sortEvent = $event;
+ treeGroup.sortField = sortField;
+ treeGroup.treeSubGroups.forEach(treeSubGroup => treeSubGroup.sortField = sortField);
+
+ this.refreshAllExpandedGroups();
+ }
+
+ selectAllGroupRows($event, group: TreeGroupData) {
+ this.selectedAlerts = [];
+
+ if ($event.target.checked) {
+ if (group.expand && group.show && group.response) {
+ this.selectedAlerts = group.response.results;
+ }
+
+ group.treeSubGroups.forEach(subGroup => {
+ if (subGroup.expand && subGroup.show && subGroup.response) {
+ this.selectedAlerts = this.selectedAlerts.concat(subGroup.response.results);
+ }
+ });
+ }
+
+ this.onSelectedAlertsChange.emit(this.selectedAlerts);
+ }
+
+ refreshAllExpandedGroups() {
+ Object.keys(this.treeGroupSubscriptionMap).forEach(key => {
+ this.getAlerts(this.treeGroupSubscriptionMap[key].group);
+ });
+ }
+
+ 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) {
+ 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/942aaaf2/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss
index 4c29e28..7d16a4f 100644
--- a/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss
+++ b/metron-interface/metron-alerts/src/app/alerts/configure-rows/configure-rows.component.scss
@@ -25,7 +25,7 @@ label {
.card {
width: 349px;
position: absolute;
- left: -140px;
+ left: -115px;
z-index: 1;
top: 50px;
border-radius: 0;
@@ -78,7 +78,7 @@ label {
.fa-sort-asc {
position: absolute;
bottom: -50px;
- left: 10px;
+ left: 44px;
font-size: 62px;
color: #333333;
z-index: 2;