METRON-1252 Build UI for grouping alerts into meta-alerts (iraghumitra via nickwallen) closes apache/metron#803
Project: http://git-wip-us.apache.org/repos/asf/metron/repo Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/d07833a2 Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/d07833a2 Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/d07833a2 Branch: refs/heads/master Commit: d07833a25470bafafdeee5e9e4054cb4a013bedb Parents: a1408d7 Author: iraghumitra <[email protected]> Authored: Mon Nov 27 17:14:13 2017 -0500 Committer: nickallen <[email protected]> Committed: Mon Nov 27 17:14:13 2017 -0500 ---------------------------------------------------------------------- .../e2e/alert-details/alert-details.po.ts | 31 ++- .../alert-details-status.e2e-spec.ts | 3 +- .../e2e/alerts-list/alerts-list.e2e-spec.ts | 18 +- .../e2e/alerts-list/alerts-list.po.ts | 63 ++++- .../configure-table/configure-table.e2e-spec.ts | 3 +- .../meta-alerts/meta-alert.e2e-spec.ts | 248 +++++++++++++++++++ .../alerts-list/meta-alerts/meta-alert.po.ts | 43 ++++ .../alerts-list/tree-view/tree-view.e2e-spec.ts | 95 ++++--- .../e2e/alerts-list/tree-view/tree-view.po.ts | 74 +++++- .../metron-alerts/e2e/utils/e2e_util.ts | 12 + .../metron-alerts/protractor.conf.js | 9 +- .../metron-alerts/src/_variables.scss | 2 + .../alert-details/alert-details-keys.pipe.ts | 31 +++ .../alert-details/alert-details.component.html | 34 ++- .../alert-details/alert-details.component.scss | 33 +++ .../alert-details/alert-details.component.ts | 44 +++- .../alert-details/alerts-details.module.ts | 6 +- .../alerts-list/alerts-list.component.html | 10 +- .../alerts/alerts-list/alerts-list.component.ts | 77 ++++-- .../alerts/alerts-list/alerts-list.module.ts | 3 +- .../src/app/alerts/alerts-list/query-builder.ts | 5 +- .../table-view/table-view.component.html | 101 +++++++- .../table-view/table-view.component.scss | 15 +- .../table-view/table-view.component.ts | 145 +++++++++-- .../alerts-list/tree-view/tree-group-data.ts | 1 + .../tree-view/tree-view.component.html | 11 +- .../tree-view/tree-view.component.scss | 17 ++ .../tree-view/tree-view.component.ts | 117 +++++++-- .../meta-alerts/meta-alerts.component.html | 52 ++++ .../meta-alerts/meta-alerts.component.scss | 58 +++++ .../alerts/meta-alerts/meta-alerts.component.ts | 79 ++++++ .../alerts/meta-alerts/meta-alerts.module.ts | 13 + .../alerts/meta-alerts/meta-alerts.routing.ts | 24 ++ .../metron-alerts/src/app/app.module.ts | 8 +- .../metron-alerts/src/app/model/alert-source.ts | 1 + .../metron-alerts/src/app/model/filter.ts | 28 ++- .../metron-alerts/src/app/model/get-request.ts | 29 +++ .../app/model/meta-alert-add-remove-request.ts | 23 ++ .../src/app/model/meta-alert-create-request.ts | 24 ++ .../src/app/model/replace-request.ts | 24 ++ .../src/app/model/search-request.ts | 2 +- .../metron-alerts/src/app/model/sort-field.ts | 5 + .../src/app/service/meta-alert.service.ts | 88 +++++++ .../src/app/service/update.service.ts | 20 +- .../src/app/shared/metron-dialog-box.ts | 4 +- .../src/app/shared/pipes/time-lapse.pipe.ts | 17 ++ .../src/app/shared/shared.module.ts | 7 +- .../shared/time-range/time-range.component.ts | 7 +- .../metron-alerts/src/app/utils/constants.ts | 5 + .../metron-alerts/src/app/utils/utils.ts | 22 +- .../src/environments/environment.e2e.ts | 4 +- metron-interface/metron-alerts/src/styles.scss | 59 +++++ 52 files changed, 1645 insertions(+), 209 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 ed71627..18cb273 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 @@ -106,11 +106,13 @@ export class MetronAlertDetailsPage { } getCommentIconCountInListView() { - return element.all(by.css('app-table-view .fa.fa-comments-o')).count(); + let commentsElement = element.all(by.css('app-table-view .fa.fa-comments-o')); + return waitForElementPresence(commentsElement).then(() => commentsElement.count()); } getCommentIconCountInTreeView() { - return element.all(by.css('app-tree-view .fa.fa-comments-o')).count(); + let commentsElement = element.all(by.css('app-tree-view .fa.fa-comments-o')); + return waitForElementPresence(commentsElement).then(() => commentsElement.count()); } waitForTextChange(element, previousText) { @@ -118,4 +120,29 @@ export class MetronAlertDetailsPage { return browser.wait(EC.not(EC.textToBePresentInElement(element, previousText))); } + getAlertNameOrId() { + let nameSelector = element(by.css('app-alert-details .editable-text')); + return waitForElementVisibility(nameSelector).then(() => nameSelector.getText()); + } + + clickRenameMetaAlert() { + element(by.css('app-alert-details .editable-text')).click(); + } + + renameMetaAlert(name: string) { + element(by.css('app-alert-details input.form-control')).sendKeys(name); + } + + cancelRename() { + element(by.css('app-alert-details .input-group .fa.fa-times')).click(); + } + + saveRename() { + element(by.css('app-alert-details .fa.fa-check')).click(); + } + + getAlertDetailsCount() { + let titleElement = element.all(by.css('app-alert-details .alert-details-title')).get(0); + return waitForElementVisibility(titleElement).then(() => element.all(by.css('app-alert-details .alert-details-title')).count()); + } } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 4e25f82..d051a78 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 @@ -50,9 +50,10 @@ describe('metron-alerts alert status', function() { it('should change alert statuses', () => { let alertId = 'c4c5e418-3938-099e-bb0d-37028a98dca8'; - + page.navigateTo(alertId); page.clickNew(); + expect(page.getAlertStatus('ANY')).toEqual('NEW'); page.clickOpen(); expect(page.getAlertStatus('NEW')).toEqual('OPEN'); expect(listPage.getAlertStatusById(alertId)).toEqual('OPEN'); http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 b0574ee..358878d 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 @@ -24,8 +24,8 @@ 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', '', '']; + let columnNames = [ '', 'Score', 'id', 'timestamp', 'source:type', 'ip_src_addr', 'enrichm...:country', + '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' ]; @@ -58,8 +58,9 @@ describe('metron-alerts App', function() { expect(page.isPausePlayRefreshButtonPresent()).toEqualBcoz(true, 'for pause/play button'); expect(page.isConfigureTableColumnsPresent()).toEqualBcoz(true, 'for alerts table column configure button'); - expect(page.getAlertTableTitle()).toEqualBcoz('Alerts (169)', 'for alerts title'); - expect(page.getActionDropdownItems()).toEqualBcoz([ 'Open', 'Dismiss', 'Escalate', 'Resolve' ], 'for default dropdown actions'); + expect(page.getChangesAlertTableTitle('Alerts (0)')).toEqualBcoz('Alerts (169)', 'for alerts title'); + expect(page.getActionDropdownItems()).toEqualBcoz([ 'Open', 'Dismiss', 'Escalate', 'Resolve', 'Add to Alert' ], + 'for default dropdown actions'); expect(page.getTableColumnNames()).toEqualBcoz(columnNames, 'for default column names for alert list table'); }); @@ -300,6 +301,9 @@ describe('metron-alerts App', function() { }); it('should have all time-range included while searching', () => { + let startDate = new Date(1505325575000); + let endDate = new Date(1505325580000); + page.clearLocalStorage(); page.clickDateSettings(); @@ -309,8 +313,10 @@ describe('metron-alerts App', function() { /* Select custom date for time range */ page.clickDateSettings(); - page.setDate(0, '2017', 'September', '13', '23', '29', '35'); - page.setDate(1, '2017', 'September', '13', '23', '29', '40'); + page.setDate(0, String(startDate.getFullYear()), startDate.toLocaleString('en-us', { month: "long" }), String(startDate.getDate()), + String(startDate.getHours()), String(startDate.getMinutes()), String(startDate.getSeconds())); + page.setDate(1, String(endDate.getFullYear()), endDate.toLocaleString('en-us', { month: "long" }), String(endDate.getDate()), + String(endDate.getHours()), String(endDate.getMinutes()), String(endDate.getSeconds())); page.selectTimeRangeApplyButton(); expect(page.getChangesAlertTableTitle('Alerts (169)')).toEqual('Alerts (5)'); http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 4a97917..45f4fee 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 * as moment from 'moment/moment'; import {waitForElementVisibility, waitForElementPresence, waitForElementInVisibility} from '../utils/e2e_util'; export class MetronAlertsPage { @@ -172,7 +173,7 @@ export class MetronAlertsPage { } clickTableText(name: string) { - waitForElementPresence(element.all(by.css('app-table-view tbody tr a'))).then(() => element.all(by.linkText(name)).get(0).click()); + waitForElementPresence(element.all(by.linkText(name))).then(() => element.all(by.linkText(name)).get(0).click()); } clickClearSearch() { @@ -278,14 +279,20 @@ export class MetronAlertsPage { }); } - getAlertStatus(rowIndex: number, previousText) { + getAlertStatus(rowIndex: number, previousText: string, colIndex = 8) { let row = element.all(by.css('app-alerts-list tbody tr')).get(rowIndex); - let column = row.all(by.css('td a')).get(8); + let column = row.all(by.css('td a')).get(colIndex); return this.waitForTextChange(column, previousText).then(() => { return column.getText(); }); } + waitForMetaAlert() { + browser.sleep(2000); + return element(by.css('button[data-name="search"]')).click() + .then(() => waitForElementPresence(element(by.css('.icon-cell.dropdown-cell')))); + } + isDateSeettingDisabled() { return element.all(by.css('app-time-range button.btn.btn-search[disabled=""]')).count().then((count) => { return (count === 1); }); } @@ -298,7 +305,7 @@ export class MetronAlertsPage { getTimeRangeTitles() { return element.all(by.css('app-time-range .title')).getText(); } - + getQuickTimeRanges() { return element.all(by.css('app-time-range .quick-ranges span')).getText(); } @@ -315,7 +322,7 @@ export class MetronAlertsPage { element.all(by.cssContainingText('.quick-ranges span', quickRange)).get(0).click(); browser.sleep(2000); } - + getTimeRangeButtonText() { return element.all(by.css('app-time-range button.btn-search span')).get(0).getText(); } @@ -351,10 +358,44 @@ export class MetronAlertsPage { } getAlertStatusById(id: string) { - return element(by.css('a[title="' + id +'"]')) + return element(by.css('a[title="' + id + '"]')) .element(by.xpath('../..')).all(by.css('td a')).get(8).getText(); } + sortTable(colName: string) { + element.all(by.css('table thead th')).all(by.linkText(colName)).get(0).click(); + } + + getCellValue(rowIndex: number, colIndex: number, previousText: string) { + let cellElement = element.all(by.css('table tbody tr')).get(rowIndex).all(by.css('td')).get(colIndex); + return this.waitForTextChange(cellElement, previousText).then(() => cellElement.getText()); + } + + expandMetaAlert(rowIndex: number) { + element.all(by.css('table tbody tr')).get(rowIndex).element(by.css('.icon-cell.dropdown-cell')).click(); + } + + getHiddenRowCount() { + return element.all(by.css('table tbody tr.d-none')).count(); + } + + getNonHiddenRowCount() { + return element.all(by.css('table tbody tr:not(.d-none)')).count(); + } + + getAllRowsCount() { + return element.all(by.css('table tbody tr')).count(); + } + + clickOnMetaAlertRow(rowIndex: number) { + element.all(by.css('table tbody tr')).get(rowIndex).all(by.css('td')).get(5).click(); + browser.sleep(2000); + } + + removeAlert(rowIndex: number) { + return element.all(by.css('app-table-view .fa-chain-broken')).get(rowIndex).click(); + } + loadSavedSearch(name: string) { element.all(by.css('app-saved-searches metron-collapse')).get(1).element(by.css('li[title="'+ name +'"]')).click(); browser.sleep(1000); @@ -376,18 +417,22 @@ export class MetronAlertsPage { let retArr = [arr[0]]; for (let i=1; i < arr.length; i++) { let dateStr = arr[i].split(' to '); - let fromTime = new Date(dateStr[0]).getTime(); - let toTime = new Date(dateStr[1]).getTime(); + let fromTime = moment.utc(dateStr[0], 'YYYY-MM-DD HH:mm:ss Z').unix() * 1000; + let toTime = moment.utc(dateStr[1], 'YYYY-MM-DD HH:mm:ss Z').unix() * 1000; retArr.push((toTime - fromTime) + ''); } return retArr; }); } - + renameColumn(name: string, value: string) { element(by.cssContainingText('app-configure-table span', name)) .element(by.xpath('../..')) .element(by.css('.input')).sendKeys(value); } + getTableCellValues(cellIndex: number, startRowIndex: number, endRowIndex: number): any { + return element.all(by.css('table tbody tr td:nth-child(' + cellIndex + ')')).getText() + .then(val => val.slice(startRowIndex, endRowIndex)); + } } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts index ddad558..6741e2f 100644 --- a/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts +++ b/metron-interface/metron-alerts/e2e/alerts-list/configure-table/configure-table.e2e-spec.ts @@ -76,8 +76,7 @@ describe('metron-alerts configure table', function() { expect(page.getChangesAlertTableTitle('Alerts (169)')).toEqual('Alerts (25)'); page.clickClearSearch(); - let columnNames = ['Score','id', 'timestamp','source:type','ip_src_addr','Country','ip_dst_addr','host','alert_status','','']; - expect(page.getTableColumnNames()).toEqualBcoz(columnNames, 'for renamed column names for alert list table'); + expect(page.getTableColumnNames()).toContain('Country', 'for renamed column names for alert list table'); }); http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts b/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts new file mode 100644 index 0000000..38bacee --- /dev/null +++ b/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts @@ -0,0 +1,248 @@ +/// <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 { 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'; +import {MetronAlertDetailsPage} from '../../alert-details/alert-details.po'; +import {MetaAlertPage} from './meta-alert.po'; +import {AlertFacetsPage} from '../alert-filters/alert-filters.po'; + +describe('meta-alerts workflow', function() { + let detailsPage: MetronAlertDetailsPage; + let tablePage: MetronAlertsPage; + let metaAlertPage: MetaAlertPage; + let treePage: TreeViewPage; + let loginPage: LoginPage; + let alertFacetsPage: AlertFacetsPage; + + beforeAll(() => { + loadTestData(); + + loginPage = new LoginPage(); + loginPage.login(); + tablePage = new MetronAlertsPage(); + treePage = new TreeViewPage(); + tablePage = new MetronAlertsPage(); + metaAlertPage = new MetaAlertPage(); + detailsPage = new MetronAlertDetailsPage(); + alertFacetsPage = new AlertFacetsPage(); + }); + + afterAll(() => { + loginPage.logout(); + deleteTestData(); + }); + + beforeEach(() => { + jasmine.addMatchers(customMatchers); + }); + + it('should have all the steps for meta alerts workflow', () => { + let comment1 = 'This is a sample comment'; + let userNameAndTimestamp = '- admin - a few seconds ago'; + let confirmText = 'Do you wish to create a meta alert with 113 selected alerts?'; + let dashRowValues = { + 'firstDashRow': ['0', '192.168.138.158', 'ALERTS', '113'], + 'secondDashRow': ['0', '192.168.66.1', 'ALERTS', '56'] + }; + + tablePage.navigateTo(); + + /* Create Meta Alert */ + treePage.selectGroup('ip_src_addr'); + expect(treePage.getDashGroupValues('192.168.138.158')).toEqualBcoz(dashRowValues.firstDashRow, 'First Dashrow to be present'); + expect(treePage.getDashGroupValues('192.168.66.1')).toEqualBcoz(dashRowValues.secondDashRow, 'Second Dashrow to be present'); + + treePage.clickOnMergeAlerts('192.168.138.158'); + expect(treePage.getConfirmationText()).toEqualBcoz(confirmText, 'confirmation text to be present'); + treePage.clickNoForConfirmation(); + + treePage.clickOnMergeAlerts('192.168.138.158'); + treePage.clickYesForConfirmation(); + + treePage.waitForElementToDisappear('192.168.138.158'); + + treePage.unGroup(); + + /* Table should have all alerts */ + tablePage.waitForMetaAlert(); + expect(tablePage.getPaginationText()).toEqualBcoz('1 - 25 of 57', 'pagination text to be present'); + expect(tablePage.getCellValue(0, 2, '(114)')).toContain('(113)', 'number of alerts in a meta alert should be correct'); + expect(tablePage.getNonHiddenRowCount()).toEqualBcoz(25, '25 rows to be visible'); + expect(tablePage.getAllRowsCount()).toEqualBcoz(138, '138 rows to be available'); + expect(tablePage.getHiddenRowCount()).toEqualBcoz(113, '113 rows to be hidden'); + tablePage.expandMetaAlert(0); + expect(tablePage.getNonHiddenRowCount()).toEqualBcoz(138, '138 rows to be visible after group expand'); + expect(tablePage.getAllRowsCount()).toEqualBcoz(138, '138 rows to be available after group expand'); + expect(tablePage.getHiddenRowCount()).toEqualBcoz(0, '0 rows to be hidden after group expand'); + + /* Meta Alert Status Change */ + tablePage.toggleAlertInList(0); + tablePage.clickActionDropdownOption('Open'); + expect(tablePage.getAlertStatus(0, 'NEW', 2)).toEqual('OPEN'); + expect(tablePage.getAlertStatus(1, 'NEW')).toEqual('OPEN'); + expect(tablePage.getAlertStatus(2, 'NEW')).toEqual('OPEN'); + + /* Details Edit Should work - add comments - remove comments - multiple alerts */ + tablePage.clickOnMetaAlertRow(0); + expect(detailsPage.getAlertDetailsCount()).toEqualBcoz(113, '113 alert details should be present'); + detailsPage.clickRenameMetaAlert(); + detailsPage.renameMetaAlert('e2e-meta-alert'); + detailsPage.cancelRename(); + expect(detailsPage.getAlertNameOrId()).not.toEqual('e2e-meta-alert'); + detailsPage.clickRenameMetaAlert(); + detailsPage.renameMetaAlert('e2e-meta-alert'); + detailsPage.saveRename(); + expect(detailsPage.getAlertNameOrId()).toEqual('e2e-meta-alert'); + + detailsPage.clickCommentsInSideNav(); + detailsPage.addCommentAndSave(comment1, 0); + expect(detailsPage.getCommentsText()).toEqual([comment1]); + expect(detailsPage.getCommentsUserNameAndTimeStamp()).toEqual([userNameAndTimestamp]); + expect(detailsPage.getCommentIconCountInListView()).toEqual(1); + + detailsPage.deleteComment(); + detailsPage.clickYesForConfirmation(); + + detailsPage.closeDetailPane(); + + /* Add to alert */ + tablePage.toggleAlertInList(3); + tablePage.clickActionDropdownOption('Add to Alert'); + expect(metaAlertPage.getPageTitle()).toEqualBcoz('Add to Alert', 'Add Alert Title should be present'); + expect(metaAlertPage.getMetaAlertsTitle()).toEqualBcoz('SELECT OPEN ALERT', 'select open alert title should be present'); + expect(metaAlertPage.getAvailableMetaAlerts()).toEqualBcoz('e2e-meta-alert (113)', 'Meta alert should be present'); + metaAlertPage.selectRadio(); + metaAlertPage.addToMetaAlert(); + expect(tablePage.getCellValue(0, 2, '(113')).toContain('(114)', 'alert count should be incremented'); + + /* Remove from alert */ + let removAlertConfirmText = 'Do you wish to remove the alert from the meta alert?'; + tablePage.removeAlert(2); + expect(treePage.getConfirmationText()).toEqualBcoz(removAlertConfirmText, 'confirmation text to remove alert from meta alert'); + treePage.clickYesForConfirmation(); + expect(tablePage.getCellValue(0, 2, '(114')).toContain('(113)', 'alert count should be decremented'); + + /* Delete Meta Alert */ + let removeMetaAlertConfirmText = 'Do you wish to remove all the alerts from meta alert?'; + tablePage.removeAlert(0); + expect(treePage.getConfirmationText()).toEqualBcoz(removeMetaAlertConfirmText, 'confirmation text to remove meta alert'); + treePage.clickYesForConfirmation(); + }); + + it('should create a meta alert from nesting of more than one level', () => { + let groupByItems = { + 'source:type': '1', + 'ip_dst_addr': '7', + 'host': '9', + 'enrichm...:country': '3', + 'ip_src_addr': '2' + }; + let alertsInMetaAlerts = [ + '82f8046d-d...03b17480dd', + '5c1825f6-7...da3abe3aec', + '9041285e-9...a04a885b53', + 'ed906df7-2...91cc54c2f3', + 'c894bbcf-3...74cf0cc1fe', + 'e63ff7ae-d...cddbe0c0b3', + '3c346bf9-b...cb04b43210', + 'dcc483af-c...7bb802b652', + 'b71f085d-6...a4904d8fcf', + '754b4f63-3...b39678207f', + 'd9430af3-e...9a18600ab2', + '9a943c94-c...3b9046b782', + 'f39dc401-3...1f9cf02cd9', + 'd887fe69-c...2fdba06dbc', + 'e38be207-b...60a43e3378', + 'eba8eccb-b...0005325a90', + 'adca96e3-1...979bf0b5f1', + '42f4ce28-8...b3d575b507', + 'aed3d10f-b...8b8a139f25', + 'a5e95569-a...0e2613b29a' + ]; + + let alertsAfterDeletedInMetaAlerts = [ + '82f8046d-d...03b17480dd', + '5c1825f6-7...da3abe3aec', + '9041285e-9...a04a885b53', + 'ed906df7-2...91cc54c2f3', + 'e63ff7ae-d...cddbe0c0b3', + '3c346bf9-b...cb04b43210', + 'dcc483af-c...7bb802b652', + 'b71f085d-6...a4904d8fcf', + '754b4f63-3...b39678207f', + 'd9430af3-e...9a18600ab2', + '9a943c94-c...3b9046b782', + 'f39dc401-3...1f9cf02cd9', + 'd887fe69-c...2fdba06dbc', + 'e38be207-b...60a43e3378', + 'eba8eccb-b...0005325a90', + 'adca96e3-1...979bf0b5f1', + '42f4ce28-8...b3d575b507', + 'aed3d10f-b...8b8a139f25', + 'a5e95569-a...0e2613b29a' + ]; + + // Create a meta alert from a group that is nested by more than 1 level + treePage.selectGroup('source:type'); + treePage.selectGroup('ip_dst_addr'); + treePage.expandDashGroup('alerts_ui_e2e'); + + treePage.clickOnMergeAlertsInTable('alerts_ui_e2e', '224.0.0.251', 0); + treePage.clickYesForConfirmation(); + + treePage.unGroup(); + tablePage.waitForMetaAlert(); + + expect(tablePage.getPaginationText()).toEqualBcoz('1 - 25 of 150', 'pagination text to be present'); + + // Meta Alert should appear in Filters + alertFacetsPage.toggleFacetState(4); + expect(alertFacetsPage.getFacetValues(4)).toEqual({'metaalert': '1' }, 'for source:type facet'); + + // Meta Alert should not appear in Groups + expect(treePage.getGroupByItemNames()).toEqualBcoz(Object.keys(groupByItems), 'Group By Elements names should be present'); + expect(treePage.getGroupByItemCounts()).toEqualBcoz(Object.keys(groupByItems).map(key => groupByItems[key]), + '5 Group By Elements values should be present'); + + + tablePage.setSearchText('guid:c894bbcf-3195-0708-aebe-0574cf0cc1fe'); + expect(tablePage.getChangesAlertTableTitle('Alerts (150)')).toEqual('Alerts (1)'); + tablePage.expandMetaAlert(0); + expect(tablePage.getAllRowsCount()).toEqual(21); + tablePage.expandMetaAlert(0); + tablePage.clickClearSearch(); + expect(tablePage.getChangesAlertTableTitle('Alerts (1)')).toEqual('Alerts (150)'); + + // Delete a meta alert from the middle and check the data + tablePage.expandMetaAlert(0); + expect(tablePage.getTableCellValues(3, 1, 21)).toEqual(alertsInMetaAlerts); + tablePage.removeAlert(5); + treePage.clickYesForConfirmation(); + expect(tablePage.getCellValue(0, 2, '(20')).toContain('(19)', 'alert count should be decremented'); + expect(tablePage.getTableCellValues(3, 1, 20)).toEqual(alertsAfterDeletedInMetaAlerts); + + //Remove the meta alert + tablePage.removeAlert(0); + treePage.clickYesForConfirmation(); + }); + +}); http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.po.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.po.ts b/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.po.ts new file mode 100644 index 0000000..762deb8 --- /dev/null +++ b/metron-interface/metron-alerts/e2e/alerts-list/meta-alerts/meta-alert.po.ts @@ -0,0 +1,43 @@ +/** + * 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} from 'protractor'; + +export class MetaAlertPage { + + getPageTitle() { + return element(by.css('app-meta-alerts .form-title')).getText(); + } + + getMetaAlertsTitle() { + return element(by.css('app-meta-alerts .title')).getText(); + } + + getAvailableMetaAlerts() { + return element(by.css('app-meta-alerts .guid-name-container div')).getText(); + } + + selectRadio() { + return element.all(by.css('app-meta-alerts .checkmark')).click(); + } + + addToMetaAlert() { + element.all(by.css('app-meta-alerts')).get(0).element(by.buttonText('ADD')).click(); + browser.sleep(2000); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 index caa3754..87636cd 100644 --- 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 @@ -135,41 +135,40 @@ describe('metron-alerts tree view', function () { 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'] - }; + let usGroupIds = ['9a969c64-b...001cb011a3','a651f7c3-1...a97d4966c9','afc36901-3...d931231ab2','d860ac35-1...f9e282d571','04a5c3d0-9...af17c06fbc']; + let frGroupIds = ['07b29c29-9...ff19eaa888','7cd91565-1...de5be54a6e','ca5bde58-a...f3a88d2df4','5d6faf83-8...b88a407647','e2883424-f...79bb8b0606']; - page.selectGroup('host'); + page.selectGroup('source:type'); + page.selectGroup('ip_dst_addr'); 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'); + expect(page.getActiveGroups()).toEqualBcoz(['source:type', 'ip_dst_addr', 'enrichments:geo:ip_dst_addr:country'], '3 groups should be selected'); + + expect(page.getDashGroupValues('alerts_ui_e2e')).toEqualBcoz(['0', 'alerts_ui_e2e', 'ALERTS', '169'], + 'Top Level Group Values should be present for alerts_ui_e2e'); + + page.expandDashGroup('alerts_ui_e2e'); + expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', '204.152.254.221', 0)).toEqualBcoz('0 204.152.254.221 (13)', + 'Second Level Group Values should be present for 204.152.254.221'); + page.expandSubGroupByPosition('alerts_ui_e2e', '204.152.254.221', 0); + expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', 'US', 0)).toEqualBcoz('0 US (13)', + 'Third Level Group Values should be present for US'); + + page.expandSubGroup('alerts_ui_e2e', 'US'); + expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', 'US', 0)).toEqualBcoz('0 US (13)', + 'Third Level Group Values should not change when expanded for US'); + expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', '04a5c3d0-9...af17c06fbc')).toEqual(usGroupIds, 'rows should be present for US'); + + + page.expandSubGroup('alerts_ui_e2e', '62.75.195.236'); + expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', 'FR', 1)).toEqualBcoz('0 FR (23)', + 'Third Level Group Values should be present for FR'); + + page.expandSubGroupByPosition('alerts_ui_e2e', 'FR', 1); + expect(page.getSubGroupValuesByPosition('alerts_ui_e2e', 'FR', 1)).toEqualBcoz('0 FR (23)', + 'Third Level Group Values should not change when expanded for FR'); + expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', 'e2883424-f...79bb8b0606')).toEqual(usGroupIds.concat(frGroupIds), 'rows should be present for FR'); + page.unGroup(); expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected'); }); @@ -177,19 +176,13 @@ describe('metron-alerts tree view', function () { 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 usTSCol = ['2017-09-13 17:59:32', '2017-09-13 17:59:42', '2017-09-13 17:59:53', '2017-09-13 18:00:02', '2017-09-13 18:00:14']; + let ruTSCol = ['2017-09-13 17:59:33', '2017-09-13 17:59:48', '2017-09-13 17:59:51', '2017-09-13 17:59:54', '2017-09-13 17:59:57']; + let frTSCol = ['2017-09-13 17:59:37', '2017-09-13 17:59:46', '2017-09-13 18:00:31', '2017-09-13 18:00:33', '2017-09-13 18:00:37']; - 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']; + let usSortedTSCol = ['2017-09-13 18:02:19', '2017-09-13 18:02:16', '2017-09-13 18:02:09', '2017-09-13 18:01:58', '2017-09-13 18:01:52']; + let ruSortedTSCol = ['2017-09-14 06:29:40', '2017-09-14 06:29:40', '2017-09-14 06:29:40', '2017-09-14 06:29:40', '2017-09-13 18:02:13']; + let frSortedTSCol = ['2017-09-14 06:29:40', '2017-09-14 04:29:40', '2017-09-13 18:02:20', '2017-09-13 18:02:05', '2017-09-13 18:02:04']; page.selectGroup('source:type'); page.selectGroup('enrichments:geo:ip_dst_addr:country'); @@ -199,14 +192,18 @@ describe('metron-alerts tree view', function () { page.expandSubGroup('alerts_ui_e2e', 'RU'); page.expandSubGroup('alerts_ui_e2e', 'FR'); - let unsortedIds = [...usIDCol, ...ruIDCol, ...frIDCol]; - let sortedIds = [...usSortedIDCol, ...ruSortedIDCol, ...frSortedIDCol]; + let unsortedTS = [...usTSCol, ...ruTSCol, ...frTSCol]; + let sortedTS = [...usSortedTSCol, ...ruSortedTSCol, ...frSortedTSCol]; + + page.sortSubGroup('alerts_ui_e2e', 'timestamp'); - expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', 'e2883424-f...79bb8b0606')).toEqual(unsortedIds, 'id should not be sorted'); + expect(page.getCellValuesFromTable('alerts_ui_e2e', 'timestamp', '2017-09-13 18:00:37')).toEqual(unsortedTS, + 'timestamp should be sorted asc'); - page.sortSubGroup('alerts_ui_e2e', 'id'); + page.sortSubGroup('alerts_ui_e2e', 'timestamp'); - expect(page.getCellValuesFromTable('alerts_ui_e2e', 'id', '436b9ecf-b...5f1ece4c4d')).toEqual(sortedIds, 'id should be sorted'); + expect(page.getCellValuesFromTable('alerts_ui_e2e', 'timestamp', '2017-09-13 18:02:04')).toEqual(sortedTS, + 'timestamp should be sorted dsc'); page.unGroup(); expect(page.getActiveGroups()).toEqualBcoz([], 'no groups should be selected'); http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 index b8472df..368f709 100644 --- 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 @@ -16,8 +16,11 @@ * limitations under the License. */ -import {browser, element, by, protractor} from 'protractor'; -import {waitForElementPresence, waitForTextChange} from '../../utils/e2e_util'; +import {browser, element, by} from 'protractor'; +import { + waitForElementPresence, waitForTextChange, waitForElementVisibility, + waitForElementInVisibility +} from '../../utils/e2e_util'; export class TreeViewPage { navigateToAlertsList() { @@ -52,7 +55,11 @@ export class TreeViewPage { } getSubGroupValues(name: string, rowName: string) { - return element(by.css('[data-name="' + name + '"] table tbody tr[data-name="' + rowName + '"]')).getText(); + return this.getSubGroupValuesByPosition(name, rowName, 0); + } + + getSubGroupValuesByPosition(name: string, rowName: string, position: number) { + return element.all(by.css('[data-name="' + name + '"] table tbody tr[data-name="' + rowName + '"]')).get(position).getText(); } selectGroup(name: string) { @@ -73,16 +80,25 @@ export class TreeViewPage { } 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); - }); + let cardElement = element(by.css('.card[data-name="' + name +'"]')); + let downArrowElement = element(by.css('.card[data-name="' + name + '"] .mrow.top-group')); + + return waitForElementVisibility(cardElement) + .then(() => browser.actions().mouseMove(cardElement).perform()) + .then(() => waitForElementVisibility(downArrowElement)) + .then(() => downArrowElement.click()) + .then(() => 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(); + return this.expandSubGroupByPosition(groupName, rowName, 0); + } + + expandSubGroupByPosition(groupName: string, rowName: string, position: number) { + let subGroupElement = element.all(by.css('[data-name="' + groupName + '"] tr[data-name="' + rowName + '"]')).get(position); + return waitForElementVisibility(subGroupElement) + .then(() => browser.actions().mouseMove(subGroupElement).perform()) + .then(() => subGroupElement.click()); } getDashGroupTableValuesForRow(name: string, rowId: number) { @@ -130,7 +146,7 @@ export class TreeViewPage { } getCellValuesFromTable(groupName: string, cellName: string, waitForAnchor: string) { - return waitForElementPresence(element(by. cssContainingText('[data-name="' + cellName + '"] a', waitForAnchor))).then(() => { + 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()); @@ -159,4 +175,40 @@ export class TreeViewPage { return column.getText(); }); } + + clickOnMergeAlerts(groupName: string) { + return element(by.css('[data-name="' + groupName + '"] .fa-link')).click(); + } + + clickOnMergeAlertsInTable(groupName: string, waitForAnchor: string, rowIndex: number) { + let elementFinder = element.all(by.css('[data-name="' + groupName + '"] table tbody tr')).get(rowIndex).element(by.css('.fa-link')); + return waitForElementVisibility(elementFinder) + .then(() => elementFinder.click()); + } + + getConfirmationText() { + let maskElement = element(by.className('modal-backdrop')); + return waitForElementVisibility(maskElement) + .then(() => element(by.css('.metron-dialog .modal-body')).getText()); + } + + clickNoForConfirmation() { + let maskElement = element(by.className('modal-backdrop')); + let closeButton = element(by.css('.metron-dialog')).element(by.buttonText('Cancel')); + waitForElementVisibility(maskElement) + .then(() => closeButton.click()) + .then(() => waitForElementInVisibility(maskElement)); + } + + clickYesForConfirmation() { + let okButton = element(by.css('.metron-dialog')).element(by.buttonText('OK')); + let maskElement = element(by.className('modal-backdrop')); + waitForElementVisibility(maskElement) + .then(() => okButton.click()) + .then(() => waitForElementInVisibility(maskElement)); + } + + waitForElementToDisappear(groupName: string) { + return waitForElementInVisibility(element.all(by.css('[data-name="' + groupName + '"]'))); + } } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 47f01e2..92476a4 100644 --- a/metron-interface/metron-alerts/e2e/utils/e2e_util.ts +++ b/metron-interface/metron-alerts/e2e/utils/e2e_util.ts @@ -47,6 +47,7 @@ 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') @@ -56,3 +57,14 @@ export function loadTestData() { export function deleteTestData() { request.delete('http://node1:9200/alerts_ui_e2e_index*'); } + +export function createMetaAlertsIndex() { + deleteMetaAlertsIndex(); + fs.createReadStream('./../../metron-deployment/packaging/ambari/metron-mpack/src/main/resources/common-services/METRON/CURRENT/package/files/metaalert_index.template') + .pipe(request.post('http://node1:9200/metaalert_index')); +} + +export function deleteMetaAlertsIndex() { + request.delete('http://node1:9200/metaalert_index*'); +} + http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 4fc25be..82f5c09 100644 --- a/metron-interface/metron-alerts/protractor.conf.js +++ b/metron-interface/metron-alerts/protractor.conf.js @@ -32,7 +32,8 @@ exports.config = { './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' + './e2e/alert-details/alert-status/alert-details-status.e2e-spec.ts', + './e2e/alerts-list/meta-alerts/meta-alert.e2e-spec.ts' ], capabilities: { 'browserName': 'chrome', @@ -59,6 +60,8 @@ exports.config = { }); }, onPrepare: function() { + var createMetaAlertsIndex = require('./e2e/utils/e2e_util').createMetaAlertsIndex; + createMetaAlertsIndex(); jasmine.getEnv().addReporter(new SpecReporter()); setTimeout(function() { browser.driver.executeScript(function() { @@ -70,5 +73,9 @@ exports.config = { browser.driver.manage().window().setSize(result.width, result.height); }); }); + }, + onComplete: function() { + var createMetaAlertsIndex = require('./e2e/utils/e2e_util').createMetaAlertsIndex; + createMetaAlertsIndex(); } }; http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 21cdfdf..d7e9359 100644 --- a/metron-interface/metron-alerts/src/_variables.scss +++ b/metron-interface/metron-alerts/src/_variables.scss @@ -90,11 +90,13 @@ $outer-space: #2E3A3F; $abbey: #58595B; $white: #FFFFFF; $iron: #D1D3D4; + $rolling-stone: #808285; $nile-blue: #18404E; $apple-blossom: #A94442; $eastern-blue-1: #1190C0; +$eastern-blue-2: #2192BF; $matisse: #1E7490; $downy: #77BBD0; $trout: #515760; http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details-keys.pipe.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details-keys.pipe.ts b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details-keys.pipe.ts new file mode 100644 index 0000000..d5756da --- /dev/null +++ b/metron-interface/metron-alerts/src/app/alerts/alert-details/alert-details-keys.pipe.ts @@ -0,0 +1,31 @@ +/** + * 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 { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'alertDetailsKeys' +}) +export class AlertDetailsKeysPipe implements PipeTransform { + + transform(value: any): any { + let keys = value ? Object.keys(value) : []; + return keys.filter(field => !field.includes(':ts') && field !== 'original_string' && field !== 'comments').sort(); + } +} + + http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 1b5330e..8b0efae 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 @@ -11,10 +11,10 @@ OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> -<div class="metron-slider-pane-details load-right-to-left dialog1x"> - <div class="container-fluid pl-0 h-100"> +<div class="metron-slider-pane-details load-right-to-left dialog1x" [ngClass]="{'is-meta-alert': isMetaAlert}"> + <div class="container-fluid pl-0 h-100" [ngClass]="{'pr-0': isMetaAlert}"> <div class="h-100 d-flex"> - <div class="nav-container"> + <div class="nav-container" *ngIf="!isMetaAlert"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link" [ngClass]="{'active': activeTab === tabs.DETAILS}" (click)="activeTab=tabs.DETAILS"> @@ -32,7 +32,20 @@ <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 class="form-title row ml-2"> + <div class="col px-0"> + <span appAlertSeverity [severity]="alertSource['threat:triage:score']"> </span> + <span> {{ alertSource['threat:triage:score'] }} </span> + </div> + <div class="px-0" style="width: 205px"> + <span [ngClass]="{'editable-text': alertSources.length > 1}" *ngIf="!showEditor" (click)="toggleNameEditor()"> {{ (alertSource.name && alertSource.name.length > 0)? alertSource.name : alertId | centerEllipses:20 }} </span> + <div class="input-group" *ngIf="showEditor"> + <input type="text" class="form-control" [(ngModel)]="alertName"> + <span class="input-group-addon" [ngClass]="{'disabled': alertName.length === 0}" (click)="saveName()" ><i class="fa fa-check" aria-hidden="true"></i></span> + <span class="input-group-addon" (click)="toggleNameEditor()"><i class="fa fa-times" aria-hidden="true"></i></span> + </div> + </div> + </div> </div> <div class="col-md-2 px-0"> <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="goBack()"></i> @@ -57,11 +70,16 @@ </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 class="ml-1 my-3 form" *ngIf="activeTab === tabs.DETAILS"> + <ng-container *ngFor="let alert of alertSources; let i = index;" > + <div class="pb-2 alert-details-title"> Alert {{ i + 1 }} of {{ alertSources.length }}</div> + <div *ngFor="let field of alert | alertDetailsKeys" class="row ml-1"> + <div class="col-6 mb-1 key">{{ field }}</div> <div class="col-6"> {{ alert[field] }} </div> + </div> + </ng-container> </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> http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 5899140..3b10c8f 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 @@ -27,6 +27,8 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + height: 35px; + line-height: 2.1 ; } .actions { @@ -71,8 +73,30 @@ } } +.alert-details-title { + font-size: 15px; +} + +.editable-text { + cursor: pointer; + border-bottom: 1px dashed $piction-blue; +} + +.input-group-addon { + height: 35px; + background: #333333; + border: 1px solid #4D4D4D; + i { + font-size: 15px; + color: #999999; + } +} .dialog1x { width: 400px; + + &.is-meta-alert { + width: 346px; + } } .nav-container { @@ -143,3 +167,12 @@ textarea { .comment-container:hover i { display: block; } + +.input-group-addon { + cursor: pointer; +} + +.disabled { + opacity: 0.5; + cursor: not-allowed; +} http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 3d5b70e..8335ad7 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 @@ -29,6 +29,7 @@ import {Patch} from '../../model/patch'; import {AlertComment} from './alert-comment'; import {AuthenticationService} from '../../service/authentication.service'; import {MetronDialogBox} from '../../shared/metron-dialog-box'; +import {META_ALERTS_INDEX, META_ALERTS_SENSOR_TYPE} from '../../utils/constants'; export enum AlertState { NEW, OPEN, ESCALATE, DISMISS, RESOLVE @@ -56,13 +57,17 @@ class AlertCommentWrapper { export class AlertDetailsComponent implements OnInit { alertId = ''; + alertName = ''; alertSourceType = ''; + showEditor = false; + isMetaAlert = false; alertIndex = ''; alertState = AlertState; tabs = Tabs; activeTab = Tabs.DETAILS; selectedAlertState: AlertState = AlertState.NEW; alertSource: AlertSource = new AlertSource(); + alertSources = []; alertFields: string[] = []; alertCommentStr = ''; alertCommentsWrapper: AlertCommentWrapper[] = []; @@ -82,14 +87,17 @@ export class AlertDetailsComponent implements OnInit { return false; } - getData() { + getData(fireToggleEditor = false) { 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' && field !== 'comments') - .sort(); - this.selectedAlertState = this.getAlertState(alert['alert_status']); - this.setComments(alert); + this.searchService.getAlert(this.alertSourceType, this.alertId).subscribe(alertSource => { + this.alertSource = alertSource; + this.selectedAlertState = this.getAlertState(alertSource['alert_status']); + this.alertSources = (alertSource.alert && alertSource.alert.length > 0) ? alertSource.alert : [alertSource]; + this.setComments(alertSource); + + if (fireToggleEditor) { + this.toggleNameEditor(); + } }); } @@ -118,6 +126,7 @@ export class AlertDetailsComponent implements OnInit { this.alertId = params['guid']; this.alertSourceType = params['sourceType']; this.alertIndex = params['index']; + this.isMetaAlert = (this.alertIndex === META_ALERTS_INDEX && this.alertSourceType !== META_ALERTS_SENSOR_TYPE) ? true : false; this.getData(); }); }; @@ -173,6 +182,27 @@ export class AlertDetailsComponent implements OnInit { }); } + toggleNameEditor() { + if (this.alertSources.length > 1) { + this.alertName = ''; + this.showEditor = !this.showEditor; + } + } + + saveName() { + if (this.alertName.length > 0) { + let patchRequest = new PatchRequest(); + patchRequest.guid = this.alertId; + patchRequest.sensorType = 'metaalert'; + patchRequest.index = META_ALERTS_INDEX; + patchRequest.patch = [new Patch('add', '/name', this.alertName)]; + + this.updateService.patch(patchRequest).subscribe(rep => { + this.getData(true); + }); + } + } + onAddComment() { let alertComment = new AlertComment(this.alertCommentStr, this.authenticationService.getCurrentUserName(), new Date().getTime()); let tAlertComments = this.alertCommentsWrapper.map(alertsWrapper => alertsWrapper.alertComment); http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 d1e36a6..9722cd3 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,13 @@ 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 { AlertDetailsKeysPipe } from './alert-details-keys.pipe'; import {AuthenticationService} from '../../service/authentication.service'; @NgModule ({ imports: [ routing, SharedModule], - declarations: [ AlertDetailsComponent ], - providers: [ AuthenticationService, AlertsService ] + declarations: [ AlertDetailsComponent, AlertDetailsKeysPipe ], + providers: [ AuthenticationService, AlertsService, UpdateService ], }) export class AlertDetailsModule { } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 63b4e41..611cdaf 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 @@ -27,7 +27,7 @@ <app-time-range (timeRangeChange)="onTimeRangeChange($event)" [disabled]="timeStampfilterPresent" [selectedTimeRange]="selectedTimeRange"> </app-time-range> </span> <span class="input-group-btn"> - <button class="btn btn-secondary btn-search" type="button" (click)="onSearch(alertSearchDirective.getSeacrhText())"></button> + <button class="btn btn-secondary btn-search" type="button" data-name="search" (click)="onSearch(alertSearchDirective.getSeacrhText())"></button> </span> </div> </div> @@ -61,6 +61,7 @@ <span class="dropdown-item" [class.disabled]="selectedAlerts.length == 0" (click)="processDismiss()">Dismiss</span> <span class="dropdown-item" [class.disabled]="selectedAlerts.length == 0" (click)="processEscalate()">Escalate</span> <span class="dropdown-item" [class.disabled]="selectedAlerts.length == 0" (click)="processResolve()">Resolve</span> + <span class="dropdown-item" [class.disabled]="selectedAlerts.length == 0 || isMetaAlertPresentInSelectedAlerts" (click)="processAddToAlert()">Add to Alert</span> </div> </div> </div> @@ -74,10 +75,10 @@ <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 class="col-xs-12 pl-0 pb-3"> + <app-group-by [facets]="groupFacets" (groupsChange)="onGroupsChange($event)"> </app-group-by> </div> - <div class="col-sm-12 px-0"> + <div class="col-xs-12 px-0"> <app-table-view #dataViewComponent [alerts]="alerts" *ngIf="queryBuilder.groupRequest.groups.length === 0" [queryBuilder]="queryBuilder" @@ -96,6 +97,7 @@ [selectedAlerts]="selectedAlerts" (onResize)="onResize()" (onAddFilter)="onAddFilter($event)" + (onRefreshData)="onRefreshData($event)" (onShowDetails)="showDetails($event)" (onSelectedAlertsChange)="onSelectedAlertsChange($event)"></app-tree-view> </div> http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 228c4f7..138606e 100644 --- a/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts +++ b/metron-interface/metron-alerts/src/app/alerts/alerts-list/alerts-list.component.ts @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {Component, OnInit, ViewChild, ElementRef, OnDestroy, ChangeDetectorRef} from '@angular/core'; +import {Component, OnInit, ViewChild, ElementRef, OnDestroy} from '@angular/core'; import {Router, NavigationStart} from '@angular/router'; import {Observable, Subscription} from 'rxjs/Rx'; @@ -39,7 +39,9 @@ import {Filter} from '../../model/filter'; import {THREAT_SCORE_FIELD_NAME, TIMESTAMP_FIELD_NAME, ALL_TIME} from '../../utils/constants'; import {TableViewComponent} from './table-view/table-view.component'; import {Pagination} from '../../model/pagination'; -import {PatchRequest} from '../../model/patch-request'; +import {META_ALERTS_SENSOR_TYPE, META_ALERTS_INDEX} from '../../utils/constants'; +import {MetaAlertService} from '../../service/meta-alert.service'; +import {Facets} from '../../model/facets'; @Component({ selector: 'app-alerts-list', @@ -59,6 +61,7 @@ export class AlertsListComponent implements OnInit, OnDestroy { refreshTimer: Subscription; pauseRefresh = false; lastPauseRefreshValue = false; + isMetaAlertPresentInSelectedAlerts = false; timeStampfilterPresent = false; selectedTimeRange = new Filter(TIMESTAMP_FIELD_NAME, ALL_TIME, false); threatScoreFieldName = THREAT_SCORE_FIELD_NAME; @@ -70,6 +73,8 @@ export class AlertsListComponent implements OnInit, OnDestroy { tableMetaData = new TableMetadata(); queryBuilder: QueryBuilder = new QueryBuilder(); pagination: Pagination = new Pagination(); + alertChangedSubscription: Subscription; + groupFacets: Facets; constructor(private router: Router, private searchService: SearchService, @@ -79,7 +84,7 @@ export class AlertsListComponent implements OnInit, OnDestroy { private clusterMetaDataService: ClusterMetaDataService, private saveSearchService: SaveSearchService, private metronDialogBox: MetronDialogBox, - private changeDetector: ChangeDetectorRef) { + private metaAlertsService: MetaAlertService) { router.events.subscribe(event => { if (event instanceof NavigationStart && event.url === '/alerts-list') { this.selectedAlerts = []; @@ -89,8 +94,12 @@ export class AlertsListComponent implements OnInit, OnDestroy { } addAlertChangedListner() { - this.updateService.alertChanged$.subscribe(patchRequest => { - this.updateAlert(patchRequest); + this.metaAlertsService.alertChanged$.subscribe(metaAlertAddRemoveRequest => { + this.updateAlert(META_ALERTS_SENSOR_TYPE, metaAlertAddRemoveRequest.metaAlertGuid, (metaAlertAddRemoveRequest.alerts === null)); + }); + + this.alertChangedSubscription = this.updateService.alertChanged$.subscribe(patchRequest => { + this.updateAlert(patchRequest.sensorType, patchRequest.guid, false); }); } @@ -125,8 +134,8 @@ export class AlertsListComponent implements OnInit, OnDestroy { } calcColumnsToDisplay() { - let availableWidth = document.documentElement.clientWidth - (200 + (15 * 4)); /* screenwidth - (navPaneWidth + (paddings))*/ - availableWidth = availableWidth - (55 + 25 + 25); /* availableWidth - (score + colunSelectIcon +selectCheckbox )*/ + let availableWidth = document.documentElement.clientWidth - (200 + (15 + 15 + 25)); /* screenwidth - (navPaneWidth + (paddings))*/ + availableWidth = availableWidth - ((20 * 3) + 55 + 25); /* availableWidth - (score + colunSelectIcon +selectCheckbox )*/ let tWidth = 0; this.alertsColumnsToDisplay = this.alertsColumns.filter(colMetaData => { if (colMetaData.type.toUpperCase() === 'DATE') { @@ -145,8 +154,8 @@ export class AlertsListComponent implements OnInit, OnDestroy { getAlertColumnNames(resetPaginationForSearch: boolean) { Observable.forkJoin( - this.configureTableService.getTableMetadata(), - this.clusterMetaDataService.getDefaultColumns() + this.configureTableService.getTableMetadata(), + this.clusterMetaDataService.getDefaultColumns() ).subscribe((response: any) => { this.prepareData(response[0], response[1], resetPaginationForSearch); }); @@ -161,6 +170,7 @@ export class AlertsListComponent implements OnInit, OnDestroy { ngOnDestroy() { this.tryStopPolling(); + this.removeAlertChangedListner(); } ngOnInit() { @@ -194,6 +204,8 @@ export class AlertsListComponent implements OnInit, OnDestroy { onSelectedAlertsChange(selectedAlerts) { this.selectedAlerts = selectedAlerts; + this.isMetaAlertPresentInSelectedAlerts = this.selectedAlerts.some(alert => (alert.source.alert && alert.source.alert.length > 0)); + if (selectedAlerts.length > 0) { this.pause(); } else { @@ -258,29 +270,34 @@ export class AlertsListComponent implements OnInit, OnDestroy { } processEscalate() { - this.updateService.updateAlertState(this.selectedAlerts, 'ESCALATE').subscribe(results => { + this.updateService.updateAlertState(this.selectedAlerts, 'ESCALATE', false).subscribe(results => { this.updateSelectedAlertStatus('ESCALATE'); }); } processDismiss() { - this.updateService.updateAlertState(this.selectedAlerts, 'DISMISS').subscribe(results => { + this.updateService.updateAlertState(this.selectedAlerts, 'DISMISS', false).subscribe(results => { this.updateSelectedAlertStatus('DISMISS'); }); } processOpen() { - this.updateService.updateAlertState(this.selectedAlerts, 'OPEN').subscribe(results => { + this.updateService.updateAlertState(this.selectedAlerts, 'OPEN', false).subscribe(results => { this.updateSelectedAlertStatus('OPEN'); }); } processResolve() { - this.updateService.updateAlertState(this.selectedAlerts, 'RESOLVE').subscribe(results => { + this.updateService.updateAlertState(this.selectedAlerts, 'RESOLVE', false).subscribe(results => { this.updateSelectedAlertStatus('RESOLVE'); }); } + processAddToAlert() { + this.metaAlertsService.selectedAlerts = this.selectedAlerts; + this.router.navigateByUrl('/alerts-list(dialog:add-to-meta-alert)'); + } + removeFilter(field: string) { this.timeStampfilterPresent = (field === TIMESTAMP_FIELD_NAME) ? false : this.timeStampfilterPresent; this.queryBuilder.removeFilter(field); @@ -311,7 +328,7 @@ export class AlertsListComponent implements OnInit, OnDestroy { } setSearchRequestSize() { - if (this.queryBuilder.groupRequest.groups.length == 0) { + if (this.queryBuilder.groupRequest.groups.length === 0) { this.queryBuilder.searchRequest.from = this.pagination.from; if (this.tableMetaData.size) { this.pagination.size = this.tableMetaData.size; @@ -345,6 +362,14 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.pagination.total = results.total; this.alerts = results.results ? results.results : []; this.setSelectedTimeRange(this.queryBuilder.filters); + this.createGroupFacets(results); + } + + private createGroupFacets(results: SearchResponse) { + this.groupFacets = JSON.parse(JSON.stringify(results.facetCounts)); + if (this.groupFacets['source:type']) { + delete this.groupFacets['source:type']['metaalert']; + } } showConfigureTable() { @@ -353,10 +378,12 @@ 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(); + let sourceType = (alert.index === META_ALERTS_INDEX && !alert.source['source:type']) + ? META_ALERTS_SENSOR_TYPE : alert.source['source:type']; + let url = '/alerts-list(dialog:details/' + sourceType + '/' + alert.source.guid + '/' + alert.index + ')'; this.router.navigateByUrl(url); } @@ -405,13 +432,22 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.searchService.interval = this.refreshInterval; } - updateAlert(patchRequest: PatchRequest) { - this.searchService.getAlert(patchRequest.sensorType, patchRequest.guid).subscribe(alertSource => { - this.alerts.filter(alert => alert.source.guid === patchRequest.guid) + updateAlert(sensorType: string, guid: string, isDelete: boolean) { + if (isDelete) { + let alertIndex = -1; + this.alerts.forEach((alert, index) => { + alertIndex = (alert.source.guid === guid) ? index : alertIndex; + }); + this.alerts.splice(alertIndex, 1); + return; + } + + this.searchService.getAlert(sensorType, guid).subscribe(alertSource => { + this.alerts.filter(alert => alert.source.guid === guid) .map(alert => alert.source = alertSource); }); } - + updateSelectedAlertStatus(status: string) { for (let selectedAlert of this.selectedAlerts) { selectedAlert.source['alert_status'] = status; @@ -420,4 +456,7 @@ export class AlertsListComponent implements OnInit, OnDestroy { this.resume(); } + removeAlertChangedListner() { + this.alertChangedSubscription.unsubscribe(); + } } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 6e0dd2a..d1c3cc0 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 @@ -21,7 +21,6 @@ import {DecimalPipe} from '@angular/common'; import {AlertsListComponent} from './alerts-list.component'; import {routing} from './alerts-list.routing'; import {SharedModule} from '../../shared/shared.module'; -import {SearchService} from '../../service/search.service'; import {MetronSorterModule} from '../../shared/metron-table/metron-sorter/metron-sorter.module'; import {ListGroupModule} from '../../shared/list-group/list-grup.module'; import {CollapseModule} from '../../shared/collapse/collapse.module'; @@ -38,7 +37,7 @@ import {TreeViewComponent} from './tree-view/tree-view.component'; ListGroupModule, CollapseModule, GroupByModule, TimeRangeModule], exports: [AlertsListComponent], declarations: [AlertsListComponent, TableViewComponent, TreeViewComponent, AlertFiltersComponent], - providers: [DecimalPipe, SearchService] + providers: [DecimalPipe] }) export class AlertsListModule { } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/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 e9f96eb..9ac5f6e 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 @@ -147,10 +147,7 @@ export class QueryBuilder { } setSort(sortBy: string, order: string) { - let sortField = new SortField(); - sortField.field = sortBy; - sortField.sortOrder = order; - + let sortField = new SortField(sortBy, order); this.searchRequest.sort = [sortField]; } http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.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 b8fd14f..d2b1108 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 @@ -15,23 +15,98 @@ <table class="table table-sm" metron-config-table [data]="alerts" [cellSelectable]="true" (onSort)="onSort($event)" style="white-space: nowrap;" (window:resize)="resize()" #table> <thead> <tr> - <th style="width:55px"> <metron-config-sorter [type]="'number'" [sortBy]="threatScoreFieldName"> Score </metron-config-sorter> </th> + <th width="15" class="dropdown-cell"> </th> + <th width="55"> <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"><input id="select-deselect-all" class="fontawesome-checkbox" type="checkbox" (click)="selectAllRows($event)"><label for="select-deselect-all"></label></th> + <th width="20" class="icon-cell"></th> + <th width="20" class="icon-cell"></th> + <th width="25"><input id="select-deselect-all" class="fontawesome-checkbox" type="checkbox" (click)="selectAllRows($event)"><label for="select-deselect-all"></label></th> </tr> </thead> <tbody> - <tr *ngFor="let alert of alerts" (click)="showDetails($event, alert)" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1}"> - <td (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])"> - <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]"> <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }} </a> </div> - </td> - <td *ngFor="let column of alertsColumnsToDisplay" #cell> - <a (click)="addFilter(column.name, getValue(alert, column, false))" title="{{getValue(alert, column, true)}}" style="color:#689AA9">{{ getValue(alert, column, true) | centerEllipses:20:cell }}</a> - </td> - <td> <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> + <ng-container *ngFor="let alert of alerts; let alertIndex = index;"> + + <ng-container *ngIf="!alert.source.alert || alert.source.alert.length === 0"> + <tr (click)="showDetails($event, alert)" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1}"> + <td width="15" class="icon-cell"></td> + <td (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])"> + <div appAlertSeverity [severity]="alert.source[threatScoreFieldName]"> + <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }} </a> + </div> + </td> + <td *ngFor="let column of alertsColumnsToDisplay" #cell> + <a (click)="addFilter(column.name, getValue(alert, column, false))" title="{{getValue(alert, column, true)}}" style="color:#689AA9"> + {{ getValue(alert,column, true) | centerEllipses:20:cell }} + </a> + </td> + <td width="20" class="icon-cell"></td> + <td width="20" class="icon-cell"> + <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> + </ng-container> + + <ng-container *ngIf="alert.source.alert && alert.source.alert.length > 0"> + <tr (click)="showDetails($event, alert)" [ngClass]="{'selected' : selectedAlerts.indexOf(alert) != -1}"> + <td width="15" class="icon-cell dropdown-cell" (click)="toggleExpandCollapse($event, alert)"> + <i class="fa" aria-hidden="true" + [ngClass]="{'fa-caret-right': metaAlertsDisplayState[alert.id] === metronAlertDisplayState.COLLAPSE, 'fa-caret-down': metaAlertsDisplayState[alert.id] === metronAlertDisplayState.EXPAND}"> + </i> + </td> + <td (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])"> + <span appAlertSeverity [severity]="alert.source[threatScoreFieldName]"> <a> {{ alert.source[threatScoreFieldName] ? alert.source[threatScoreFieldName] : '-' }} </a> </span> + </td> + <td [attr.colspan]="alertsColumnsToDisplay.length - 1"> + <a (click)="addFilter('guid', alert.id)" [attr.title]="alert.id" style="color:#689AA9"> {{ alert.source['name'] ? alert.source['name'] : alert.id | centerEllipses:20:cell }}</a> + <span> ({{ alert.source.alert.length }})</span> + </td> + <td> + <a *ngIf="isStatusFieldPresent" (click)="addFilter('alert_status', alert.source['alert_status'])" style="color:#689AA9"> + {{ alert.source['alert_status'] ?alert.source['alert_status'] : 'New' | centerEllipses:20:cell }} + </a> + </td> + <td width="20" class="icon-cell" (click)="deleteMetaAlert($event, alert, alertIndex)"> + <i class="fa fa-chain-broken" aria-hidden="true"></i> + </td> + <td width="20" class="icon-cell"> + <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 *ngFor="let metaAlerts of alert.source.alert; let metaAlertIndex = index;" (click)="showMetaAlertDetails($event, metaAlerts)" + [ngClass]="{'selected' : selectedAlerts.indexOf(metaAlerts) != -1 , 'd-none': metaAlertsDisplayState[alert.id] === metronAlertDisplayState.COLLAPSE}"> + <td width="15" class="icon-cell" class="dropdown-cell"></td> + <td (click)="addFilter(threatScoreFieldName, alert.source[threatScoreFieldName])" style="padding-left: 15px"> + <div appAlertSeverity [severity]="metaAlerts[threatScoreFieldName]"> + <a> {{ metaAlerts[threatScoreFieldName] ? metaAlerts[threatScoreFieldName] : '-' }} </a> + </div> + </td> + <td *ngFor="let column of alertsColumnsToDisplay"> + <a *ngIf="column.name !== 'alert_status'" (click)="addFilter(column.name, getValueFromSource(metaAlerts, column, false))" title="{{ getValueFromSource(metaAlerts, column, true) }}" style="color:#689AA9"> + {{ getValueFromSource(metaAlerts, column, true) | centerEllipses:20:cell }} + </a> + <a *ngIf="column.name === 'alert_status'" (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 width="20" class="icon-cell" (click)="deleteOneAlertFromMetaAlert($event, alert, metaAlertIndex)"> + <i class="fa fa-chain-broken" aria-hidden="true"></i> + </td> + <td width="20" class="icon-cell"> + <i class="fa fa-comments-o" aria-hidden="true" *ngIf="metaAlerts.comments && metaAlerts.comments.length > 0"></i> + </td> + <td></td> + </tr> + </ng-container> + + </ng-container> </tbody> </table> </div> http://git-wip-us.apache.org/repos/asf/metron/blob/d07833a2/metron-interface/metron-alerts/src/app/alerts/alerts-list/table-view/table-view.component.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 eec7f92..f648ab2 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 @@ -24,4 +24,17 @@ .configure-table-icon { font-size: 16px; cursor: pointer; -} \ No newline at end of file +} + +.fa-chain-broken { + color: $piction-blue; +} + +.dropdown-cell { + padding-left: 0.6rem; +} + +.icon-cell { + padding: 0.3rem 3px; +} +
