You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by rm...@apache.org on 2017/04/11 13:51:17 UTC
[06/12] incubator-metron git commit: METRON-623 Management UI
[contributed by Raghu Mitra Kandikonda and Ryan Merriman] closes
apache/incubator-metron#489
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.spec.ts
new file mode 100644
index 0000000..5534bea
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.spec.ts
@@ -0,0 +1,742 @@
+/**
+ * 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 {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {SpyLocation} from '@angular/common/testing';
+import {Http, ResponseOptions, RequestOptions, Response} from '@angular/http';
+
+import {DebugElement, Inject} from '@angular/core';
+import {By} from '@angular/platform-browser';
+import {Router, NavigationStart} from '@angular/router';
+import {Observable} from 'rxjs/Observable';
+import {SensorParserListComponent} from './sensor-parser-list.component';
+import {SensorParserConfigService} from '../../service/sensor-parser-config.service';
+import {MetronAlerts} from '../../shared/metron-alerts';
+import {TopologyStatus} from '../../model/topology-status';
+import {SensorParserConfig} from '../../model/sensor-parser-config';
+import {AuthenticationService} from '../../service/authentication.service';
+import {SensorParserListModule} from './sensor-parser-list.module';
+import {MetronDialogBox} from '../../shared/metron-dialog-box';
+import {Sort} from '../../util/enums';
+import 'jquery';
+import {SensorParserConfigHistoryService} from '../../service/sensor-parser-config-history.service';
+import {SensorParserConfigHistory} from '../../model/sensor-parser-config-history';
+import {APP_CONFIG, METRON_REST_CONFIG} from '../../app.config';
+import {StormService} from '../../service/storm.service';
+import {IAppConfig} from '../../app.config.interface';
+
+class MockAuthenticationService extends AuthenticationService {
+
+ constructor(private http2: Http, private router2: Router, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, router2, config2);
+ }
+
+ public checkAuthentication() {
+ }
+
+ public getCurrentUser(options: RequestOptions): Observable<Response> {
+ return Observable.create(observer => {
+ observer.next(new Response(new ResponseOptions({body: 'test'})));
+ observer.complete();
+ });
+ }
+}
+
+class MockSensorParserConfigHistoryService extends SensorParserConfigHistoryService {
+
+ private allSensorParserConfigHistory: SensorParserConfigHistory[];
+
+ constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, config2);
+ }
+
+ public setSensorParserConfigHistoryForTest(allSensorParserConfigHistory: SensorParserConfigHistory[]) {
+ this.allSensorParserConfigHistory = allSensorParserConfigHistory;
+ }
+
+ public getAll(): Observable<SensorParserConfigHistory[]> {
+ return Observable.create(observer => {
+ observer.next(this.allSensorParserConfigHistory);
+ observer.complete();
+ });
+ }
+}
+
+class MockSensorParserConfigService extends SensorParserConfigService {
+ private sensorParserConfigs: SensorParserConfig[];
+
+ constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, config2);
+ }
+
+ public setSensorParserConfigForTest(sensorParserConfigs: SensorParserConfig[]) {
+ this.sensorParserConfigs = sensorParserConfigs;
+ }
+
+ public getAll(): Observable<SensorParserConfig[]> {
+ return Observable.create(observer => {
+ observer.next(this.sensorParserConfigs);
+ observer.complete();
+ });
+ }
+
+ public deleteSensorParserConfigs(sensors: SensorParserConfig[]): Observable<{success: Array<string>, failure: Array<string>}> {
+ let result: {success: Array<string>, failure: Array<string>} = {success: [], failure: []};
+ let observable = Observable.create((observer => {
+ for (let i = 0; i < sensors.length; i++) {
+ result.success.push(sensors[i].sensorTopic);
+ }
+ observer.next(result);
+ observer.complete();
+ }));
+ return observable;
+ }
+}
+
+class MockStormService extends StormService {
+ private topologyStatuses: TopologyStatus[];
+
+ constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, config2);
+ }
+
+ public setTopologyStatusForTest(topologyStatuses: TopologyStatus[]) {
+ this.topologyStatuses = topologyStatuses;
+ }
+
+ public pollGetAll(): Observable<TopologyStatus[]> {
+ return Observable.create(observer => {
+ observer.next(this.topologyStatuses);
+ observer.complete();
+ });
+ }
+
+ public getAll(): Observable<TopologyStatus[]> {
+ return Observable.create(observer => {
+ observer.next(this.topologyStatuses);
+ observer.complete();
+ });
+ }
+}
+
+class MockRouter {
+ events: Observable<Event> = Observable.create(observer => {
+ observer.next(new NavigationStart(1, '/sensors'));
+ observer.complete();
+ });
+
+ navigateByUrl(url: string) {
+ }
+}
+
+class MockMetronDialogBox {
+
+ public showConfirmationMessage(message: string) {
+ return Observable.create(observer => {
+ observer.next(true);
+ observer.complete();
+ });
+ }
+}
+
+describe('Component: SensorParserList', () => {
+
+ let comp: SensorParserListComponent;
+ let fixture: ComponentFixture<SensorParserListComponent>;
+ let authenticationService: MockAuthenticationService;
+ let sensorParserConfigService: MockSensorParserConfigService;
+ let stormService: MockStormService;
+ let sensorParserConfigHistoryService: MockSensorParserConfigHistoryService;
+ let router: Router;
+ let metronAlerts: MetronAlerts;
+ let metronDialog: MetronDialogBox;
+ let dialogEl: DebugElement;
+
+ beforeEach(async(() => {
+
+ TestBed.configureTestingModule({
+ imports: [SensorParserListModule],
+ providers: [
+ {provide: Http},
+ {provide: Location, useClass: SpyLocation},
+ {provide: AuthenticationService, useClass: MockAuthenticationService},
+ {provide: SensorParserConfigService, useClass: MockSensorParserConfigService},
+ {provide: StormService, useClass: MockStormService},
+ {provide: SensorParserConfigHistoryService, useClass: MockSensorParserConfigHistoryService},
+ {provide: Router, useClass: MockRouter},
+ {provide: MetronDialogBox, useClass: MockMetronDialogBox},
+ {provide: APP_CONFIG, useValue: METRON_REST_CONFIG},
+ MetronAlerts
+ ]
+ }).compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(SensorParserListComponent);
+ comp = fixture.componentInstance;
+ authenticationService = fixture.debugElement.injector.get(AuthenticationService);
+ sensorParserConfigService = fixture.debugElement.injector.get(SensorParserConfigService);
+ stormService = fixture.debugElement.injector.get(StormService);
+ sensorParserConfigHistoryService = fixture.debugElement.injector.get(SensorParserConfigHistoryService);
+ router = fixture.debugElement.injector.get(Router);
+ metronAlerts = fixture.debugElement.injector.get(MetronAlerts);
+ metronDialog = fixture.debugElement.injector.get(MetronDialogBox);
+ dialogEl = fixture.debugElement.query(By.css('.primary'));
+ });
+
+ }));
+
+ it('should create an instance', async(() => {
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+ expect(component).toBeDefined();
+ fixture.destroy();
+
+ }));
+
+ it('getSensors should call getStatus and poll status and all variables should be initialised', async(() => {
+ let sensorParserConfigHistory1 = new SensorParserConfigHistory();
+ let sensorParserConfigHistory2 = new SensorParserConfigHistory();
+ let sensorParserConfig1 = new SensorParserConfig();
+ let sensorParserConfig2 = new SensorParserConfig();
+
+ sensorParserConfig1.sensorTopic = 'squid';
+ sensorParserConfig2.sensorTopic = 'bro';
+ sensorParserConfigHistory1.config = sensorParserConfig1;
+ sensorParserConfigHistory2.config = sensorParserConfig2;
+
+ let sensorParserStatus1 = new TopologyStatus();
+ let sensorParserStatus2 = new TopologyStatus();
+ sensorParserStatus1.name = 'squid';
+ sensorParserStatus1.status = 'KILLED';
+ sensorParserStatus2.name = 'bro';
+ sensorParserStatus2.status = 'KILLED';
+
+ sensorParserConfigHistoryService.setSensorParserConfigHistoryForTest([sensorParserConfigHistory1, sensorParserConfigHistory2]);
+ stormService.setTopologyStatusForTest([sensorParserStatus1, sensorParserStatus2]);
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ component.enableAutoRefresh = false;
+
+ component.ngOnInit();
+
+ expect(component.sensors[0]).toEqual(sensorParserConfigHistory1);
+ expect(component.sensors[1]).toEqual(sensorParserConfigHistory2);
+ expect(component.sensorsStatus[0]).toEqual(Object.assign(new TopologyStatus(), sensorParserStatus1));
+ expect(component.sensorsStatus[1]).toEqual(Object.assign(new TopologyStatus(), sensorParserStatus2));
+ expect(component.selectedSensors).toEqual([]);
+ expect(component.count).toEqual(2);
+
+ fixture.destroy();
+
+ }));
+
+ it('getParserType should return the Type of Parser', async(() => {
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ let sensorParserConfig1 = new SensorParserConfig();
+ sensorParserConfig1.sensorTopic = 'squid';
+ sensorParserConfig1.parserClassName = 'org.apache.metron.parsers.GrokParser';
+ let sensorParserConfig2 = new SensorParserConfig();
+ sensorParserConfig2.sensorTopic = 'bro';
+ sensorParserConfig2.parserClassName = 'org.apache.metron.parsers.bro.BasicBroParser';
+
+ expect(component.getParserType(sensorParserConfig1)).toEqual('Grok');
+ expect(component.getParserType(sensorParserConfig2)).toEqual('Bro');
+
+ fixture.destroy();
+
+ }));
+
+ it('navigateToSensorEdit should set selected sensor and change url', async(() => {
+ let event = new Event('mouse');
+ event.stopPropagation = jasmine.createSpy('stopPropagation');
+
+ spyOn(router, 'navigateByUrl');
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ let sensorParserConfig1 = new SensorParserConfig();
+ sensorParserConfig1.sensorTopic = 'squid';
+ component.navigateToSensorEdit(sensorParserConfig1, event);
+
+ let expectStr = router.navigateByUrl['calls'].argsFor(0);
+ expect(expectStr).toEqual(['/sensors(dialog:sensors-config/squid)']);
+
+ expect(event.stopPropagation).toHaveBeenCalled();
+
+ fixture.destroy();
+ }));
+
+ it('addAddSensor should change the URL', async(() => {
+ spyOn(router, 'navigateByUrl');
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ component.addAddSensor();
+
+ let expectStr = router.navigateByUrl['calls'].argsFor(0);
+ expect(expectStr).toEqual(['/sensors(dialog:sensors-config/new)']);
+
+ fixture.destroy();
+ }));
+
+
+ it('onRowSelected should add add/remove items from the selected stack', async(() => {
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+ let event = {target: {checked: true}};
+
+ let sensorParserConfigHistory = new SensorParserConfigHistory();
+ let sensorParserConfig = new SensorParserConfig();
+
+ sensorParserConfig.sensorTopic = 'squid';
+ sensorParserConfigHistory.config = sensorParserConfig;
+
+ component.onRowSelected(sensorParserConfigHistory, event);
+
+ expect(component.selectedSensors[0]).toEqual(sensorParserConfigHistory);
+
+ event = {target: {checked: false}};
+
+ component.onRowSelected(sensorParserConfigHistory, event);
+ expect(component.selectedSensors).toEqual([]);
+
+ fixture.destroy();
+ }));
+
+ it('onSelectDeselectAll should populate items into selected stack', async(() => {
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ let sensorParserConfig1 = new SensorParserConfig();
+ let sensorParserConfig2 = new SensorParserConfig();
+ let sensorParserConfigHistory1 = new SensorParserConfigHistory();
+ let sensorParserConfigHistory2 = new SensorParserConfigHistory();
+
+ sensorParserConfig1.sensorTopic = 'squid';
+ sensorParserConfigHistory1.config = sensorParserConfig1;
+ sensorParserConfig2.sensorTopic = 'bro';
+ sensorParserConfigHistory2.config = sensorParserConfig2;
+
+ component.sensors.push(sensorParserConfigHistory1);
+ component.sensors.push(sensorParserConfigHistory2);
+
+ let event = {target: {checked: true}};
+
+ component.onSelectDeselectAll(event);
+
+ expect(component.selectedSensors).toEqual([sensorParserConfigHistory1, sensorParserConfigHistory2]);
+
+ event = {target: {checked: false}};
+
+ component.onSelectDeselectAll(event);
+
+ expect(component.selectedSensors).toEqual([]);
+
+ fixture.destroy();
+ }));
+
+ it('onSensorRowSelect should change the url and updated the selected items stack', async(() => {
+
+ let sensorParserConfig1 = new SensorParserConfig();
+ sensorParserConfig1.sensorTopic = 'squid';
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+ let event = {target: {type: 'div', parentElement: {firstChild: {type: 'div'}}}};
+
+ sensorParserConfigService.setSeletedSensor(sensorParserConfig1);
+ component.onSensorRowSelect(sensorParserConfig1, event);
+
+ expect(sensorParserConfigService.getSelectedSensor()).toEqual(null);
+
+ component.onSensorRowSelect(sensorParserConfig1, event);
+
+ expect(sensorParserConfigService.getSelectedSensor()).toEqual(sensorParserConfig1);
+
+ sensorParserConfigService.setSeletedSensor(sensorParserConfig1);
+ event = {target: {type: 'checkbox', parentElement: {firstChild: {type: 'div'}}}};
+
+ component.onSensorRowSelect(sensorParserConfig1, event);
+
+ expect(sensorParserConfigService.getSelectedSensor()).toEqual(sensorParserConfig1);
+
+ fixture.destroy();
+ }));
+
+ it('onSensorRowSelect should change the url and updated the selected items stack', async(() => {
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ let sensorParserConfigHistory = new SensorParserConfigHistory();
+ let sensorParserConfig = new SensorParserConfig();
+
+ sensorParserConfig.sensorTopic = 'squid';
+ sensorParserConfigHistory.config = sensorParserConfig;
+
+ component.toggleStartStopInProgress(sensorParserConfigHistory);
+ expect(sensorParserConfig['startStopInProgress']).toEqual(true);
+
+ component.toggleStartStopInProgress(sensorParserConfigHistory);
+ expect(sensorParserConfig['startStopInProgress']).toEqual(false);
+ }));
+
+ it('onDeleteSensor should call the appropriate url', async(() => {
+
+ spyOn(metronAlerts, 'showSuccessMessage');
+ spyOn(metronDialog, 'showConfirmationMessage').and.callThrough();
+
+ let event = new Event('mouse');
+ event.stopPropagation = jasmine.createSpy('stopPropagation');
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+ let sensorParserConfigHistory1 = new SensorParserConfigHistory();
+ let sensorParserConfigHistory2 = new SensorParserConfigHistory();
+ let sensorParserConfig1 = new SensorParserConfig();
+ let sensorParserConfig2 = new SensorParserConfig();
+
+ sensorParserConfig1.sensorTopic = 'squid';
+ sensorParserConfig2.sensorTopic = 'bro';
+ sensorParserConfigHistory1.config = sensorParserConfig1;
+ sensorParserConfigHistory2.config = sensorParserConfig2;
+
+ component.selectedSensors.push(sensorParserConfigHistory1);
+ component.selectedSensors.push(sensorParserConfigHistory2);
+
+ component.onDeleteSensor();
+
+ expect(metronAlerts.showSuccessMessage).toHaveBeenCalled();
+
+ component.deleteSensor(event, [sensorParserConfigHistory1.config]);
+
+ expect(metronDialog.showConfirmationMessage).toHaveBeenCalled();
+ expect(metronDialog.showConfirmationMessage['calls'].count()).toEqual(2);
+ expect(metronDialog.showConfirmationMessage['calls'].count()).toEqual(2);
+ expect(metronDialog.showConfirmationMessage['calls'].all()[0].args).toEqual(['Are you sure you want to delete sensor(s) squid, bro ?']);
+ expect(metronDialog.showConfirmationMessage['calls'].all()[1].args).toEqual(['Are you sure you want to delete sensor(s) squid ?']);
+
+ expect(event.stopPropagation).toHaveBeenCalled();
+
+ fixture.destroy();
+
+ }));
+
+ it('onStopSensor should call the appropriate url', async(() => {
+ let event = new Event('mouse');
+ event.stopPropagation = jasmine.createSpy('stopPropagation');
+
+ let sensorParserConfigHistory1 = new SensorParserConfigHistory();
+ let sensorParserConfig1 = new SensorParserConfig();
+
+ sensorParserConfig1.sensorTopic = 'squid';
+ sensorParserConfigHistory1.config = sensorParserConfig1;
+
+ let observableToReturn = Observable.create(observer => {
+ observer.next({status: 'success', message: 'Some Message'});
+ observer.complete();
+ });
+
+ spyOn(metronAlerts, 'showSuccessMessage');
+ spyOn(stormService, 'stopParser').and.returnValue(observableToReturn);
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ component.onStopSensor(sensorParserConfigHistory1, event);
+
+ expect(stormService.stopParser).toHaveBeenCalled();
+ expect(event.stopPropagation).toHaveBeenCalled();
+ expect(metronAlerts.showSuccessMessage).toHaveBeenCalled();
+
+ fixture.destroy();
+ }));
+
+ it('onStartSensor should call the appropriate url', async(() => {
+ let event = new Event('mouse');
+ event.stopPropagation = jasmine.createSpy('stopPropagation');
+
+ let sensorParserConfigHistory1 = new SensorParserConfigHistory();
+ let sensorParserConfig1 = new SensorParserConfig();
+
+ sensorParserConfig1.sensorTopic = 'squid';
+ sensorParserConfigHistory1.config = sensorParserConfig1;
+
+ let observableToReturn = Observable.create(observer => {
+ observer.next({status: 'success', message: 'Some Message'});
+ observer.complete();
+ });
+
+ spyOn(metronAlerts, 'showSuccessMessage');
+ spyOn(stormService, 'startParser').and.returnValue(observableToReturn);
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ component.onStartSensor(sensorParserConfigHistory1, event);
+
+ expect(stormService.startParser).toHaveBeenCalled();
+ expect(event.stopPropagation).toHaveBeenCalled();
+ expect(metronAlerts.showSuccessMessage).toHaveBeenCalled();
+
+ fixture.destroy();
+ }));
+
+ it('onEnableSensor should call the appropriate url', async(() => {
+ let event = new Event('mouse');
+ event.stopPropagation = jasmine.createSpy('stopPropagation');
+
+ let sensorParserConfigHistory1 = new SensorParserConfigHistory();
+ let sensorParserConfig1 = new SensorParserConfig();
+
+ sensorParserConfig1.sensorTopic = 'squid';
+ sensorParserConfigHistory1.config = sensorParserConfig1;
+
+ let observableToReturn = Observable.create(observer => {
+ observer.next({status: 'success', message: 'Some Message'});
+ observer.complete();
+ });
+
+ spyOn(metronAlerts, 'showSuccessMessage');
+ spyOn(stormService, 'activateParser').and.returnValue(observableToReturn);
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ component.onEnableSensor(sensorParserConfigHistory1, event);
+
+ expect(stormService.activateParser).toHaveBeenCalled();
+ expect(event.stopPropagation).toHaveBeenCalled();
+ expect(metronAlerts.showSuccessMessage).toHaveBeenCalled();
+
+ fixture.destroy();
+ }));
+
+ it('onDisableSensor should call the appropriate url', async(() => {
+ let event = new Event('mouse');
+ event.stopPropagation = jasmine.createSpy('stopPropagation');
+
+ let sensorParserConfigHistory1 = new SensorParserConfigHistory();
+ let sensorParserConfig1 = new SensorParserConfig();
+
+ sensorParserConfig1.sensorTopic = 'squid';
+ sensorParserConfigHistory1.config = sensorParserConfig1;
+
+ let observableToReturn = Observable.create(observer => {
+ observer.next({status: 'success', message: 'Some Message'});
+ observer.complete();
+ });
+
+ spyOn(metronAlerts, 'showSuccessMessage');
+ spyOn(stormService, 'deactivateParser').and.returnValue(observableToReturn);
+
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ component.onDisableSensor(sensorParserConfigHistory1, event);
+
+ expect(stormService.deactivateParser).toHaveBeenCalled();
+ expect(event.stopPropagation).toHaveBeenCalled();
+ expect(metronAlerts.showSuccessMessage).toHaveBeenCalled();
+
+ fixture.destroy();
+ }));
+
+ it('onStartSensors/onStopSensors should call start on all sensors that have status != ' +
+ 'Running and status != Running respectively', async(() => {
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ spyOn(component, 'onStartSensor');
+ spyOn(component, 'onStopSensor');
+ spyOn(component, 'onDisableSensor');
+ spyOn(component, 'onEnableSensor');
+
+ let sensorParserConfigHistory1 = new SensorParserConfigHistory();
+ let sensorParserConfigHistory2 = new SensorParserConfigHistory();
+ let sensorParserConfigHistory3 = new SensorParserConfigHistory();
+ let sensorParserConfigHistory4 = new SensorParserConfigHistory();
+ let sensorParserConfigHistory5 = new SensorParserConfigHistory();
+ let sensorParserConfigHistory6 = new SensorParserConfigHistory();
+ let sensorParserConfigHistory7 = new SensorParserConfigHistory();
+
+ let sensorParserConfig1 = new SensorParserConfig();
+ let sensorParserConfig2 = new SensorParserConfig();
+ let sensorParserConfig3 = new SensorParserConfig();
+ let sensorParserConfig4 = new SensorParserConfig();
+ let sensorParserConfig5 = new SensorParserConfig();
+ let sensorParserConfig6 = new SensorParserConfig();
+ let sensorParserConfig7 = new SensorParserConfig();
+
+ sensorParserConfig1.sensorTopic = 'squid';
+ sensorParserConfigHistory1['status'] = 'Running';
+ sensorParserConfigHistory1.config = sensorParserConfig1;
+
+ sensorParserConfig2.sensorTopic = 'bro';
+ sensorParserConfigHistory2['status'] = 'Stopped';
+ sensorParserConfigHistory2.config = sensorParserConfig2;
+
+ sensorParserConfig3.sensorTopic = 'test';
+ sensorParserConfigHistory3['status'] = 'Stopped';
+ sensorParserConfigHistory3.config = sensorParserConfig3;
+
+ sensorParserConfig4.sensorTopic = 'test1';
+ sensorParserConfigHistory4['status'] = 'Stopped';
+ sensorParserConfigHistory4.config = sensorParserConfig4;
+
+ sensorParserConfig5.sensorTopic = 'test2';
+ sensorParserConfigHistory5['status'] = 'Running';
+ sensorParserConfigHistory5.config = sensorParserConfig5;
+
+ sensorParserConfig6.sensorTopic = 'test2';
+ sensorParserConfigHistory6['status'] = 'Disabled';
+ sensorParserConfigHistory6.config = sensorParserConfig6;
+
+ sensorParserConfig7.sensorTopic = 'test3';
+ sensorParserConfigHistory7['status'] = 'Disabled';
+ sensorParserConfigHistory7.config = sensorParserConfig7;
+
+ component.selectedSensors = [sensorParserConfigHistory1, sensorParserConfigHistory2, sensorParserConfigHistory3,
+ sensorParserConfigHistory4, sensorParserConfigHistory5, sensorParserConfigHistory6, sensorParserConfigHistory7];
+
+ component.onStartSensors();
+ expect(component.onStartSensor['calls'].count()).toEqual(3);
+
+ component.onStopSensors();
+ expect(component.onStopSensor['calls'].count()).toEqual(4);
+
+ component.onDisableSensors();
+ expect(component.onDisableSensor['calls'].count()).toEqual(2);
+
+ component.onEnableSensors();
+ expect(component.onEnableSensor['calls'].count()).toEqual(2);
+
+ fixture.destroy();
+ }));
+
+ it('sort', async(() => {
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ component.sensors = [
+ Object.assign(new SensorParserConfigHistory(), {
+ 'config': {
+ 'parserClassName': 'org.apache.metron.parsers.GrokParser',
+ 'sensorTopic': 'abc',
+ },
+ 'createdBy': 'raghu',
+ 'modifiedBy': 'abc',
+ 'createdDate': '2016-11-25 09:09:12',
+ 'modifiedByDate': '2016-11-25 09:09:12'
+ }),
+ Object.assign(new SensorParserConfigHistory(), {
+ 'config': {
+ 'parserClassName': 'org.apache.metron.parsers.Bro',
+ 'sensorTopic': 'plm',
+ },
+ 'createdBy': 'raghu',
+ 'modifiedBy': 'plm',
+ 'createdDate': '2016-11-25 12:39:21',
+ 'modifiedByDate': '2016-11-25 12:39:21'
+ }),
+ Object.assign(new SensorParserConfigHistory(), {
+ 'config': {
+ 'parserClassName': 'org.apache.metron.parsers.GrokParser',
+ 'sensorTopic': 'xyz',
+ },
+ 'createdBy': 'raghu',
+ 'modifiedBy': 'xyz',
+ 'createdDate': '2016-11-25 12:44:03',
+ 'modifiedByDate': '2016-11-25 12:44:03'
+ })
+ ];
+
+ component.onSort({sortBy: 'sensorTopic', sortOrder: Sort.ASC});
+ expect(component.sensors[0].config.sensorTopic).toEqual('abc');
+ expect(component.sensors[1].config.sensorTopic).toEqual('plm');
+ expect(component.sensors[2].config.sensorTopic).toEqual('xyz');
+
+ component.onSort({sortBy: 'sensorTopic', sortOrder: Sort.DSC});
+ expect(component.sensors[0].config.sensorTopic).toEqual('xyz');
+ expect(component.sensors[1].config.sensorTopic).toEqual('plm');
+ expect(component.sensors[2].config.sensorTopic).toEqual('abc');
+
+ component.onSort({sortBy: 'parserClassName', sortOrder: Sort.ASC});
+ expect(component.sensors[0].config.parserClassName).toEqual('org.apache.metron.parsers.Bro');
+ expect(component.sensors[1].config.parserClassName).toEqual('org.apache.metron.parsers.GrokParser');
+ expect(component.sensors[2].config.parserClassName).toEqual('org.apache.metron.parsers.GrokParser');
+
+ component.onSort({sortBy: 'parserClassName', sortOrder: Sort.DSC});
+ expect(component.sensors[0].config.parserClassName).toEqual('org.apache.metron.parsers.GrokParser');
+ expect(component.sensors[1].config.parserClassName).toEqual('org.apache.metron.parsers.GrokParser');
+ expect(component.sensors[2].config.parserClassName).toEqual('org.apache.metron.parsers.Bro');
+
+ component.onSort({sortBy: 'modifiedBy', sortOrder: Sort.ASC});
+ expect(component.sensors[0].modifiedBy).toEqual('abc');
+ expect(component.sensors[1].modifiedBy).toEqual('plm');
+ expect(component.sensors[2].modifiedBy).toEqual('xyz');
+
+ component.onSort({sortBy: 'modifiedBy', sortOrder: Sort.DSC});
+ expect(component.sensors[0].modifiedBy).toEqual('xyz');
+ expect(component.sensors[1].modifiedBy).toEqual('plm');
+ expect(component.sensors[2].modifiedBy).toEqual('abc');
+
+ }));
+
+ it('sort', async(() => {
+ let component: SensorParserListComponent = fixture.componentInstance;
+
+ component.sensors = [
+ Object.assign(new SensorParserConfigHistory(), {
+ 'config': {
+ 'parserClassName': 'org.apache.metron.parsers.GrokParser',
+ 'sensorTopic': 'abc',
+ },
+ 'createdBy': 'raghu',
+ 'modifiedBy': 'abc',
+ 'createdDate': '2016-11-25 09:09:12',
+ 'modifiedByDate': '2016-11-25 09:09:12'
+ })];
+
+ component.sensorsStatus = [
+ Object.assign(new TopologyStatus(), {
+ 'name': 'abc',
+ 'status': 'ACTIVE',
+ 'latency': '10',
+ 'throughput': '23'
+ })
+ ];
+
+ component.updateSensorStatus();
+ expect(component.sensors[0]['status']).toEqual('Running');
+ expect(component.sensors[0]['latency']).toEqual('10s');
+ expect(component.sensors[0]['throughput']).toEqual('23kb/s');
+
+ component.sensorsStatus[0].status = 'KILLED';
+ component.updateSensorStatus();
+ expect(component.sensors[0]['status']).toEqual('Stopped');
+ expect(component.sensors[0]['latency']).toEqual('-');
+ expect(component.sensors[0]['throughput']).toEqual('-');
+
+ component.sensorsStatus[0].status = 'INACTIVE';
+ component.updateSensorStatus();
+ expect(component.sensors[0]['status']).toEqual('Disabled');
+ expect(component.sensors[0]['latency']).toEqual('-');
+ expect(component.sensors[0]['throughput']).toEqual('-');
+
+ component.sensorsStatus = [];
+ component.updateSensorStatus();
+ expect(component.sensors[0]['status']).toEqual('Stopped');
+ expect(component.sensors[0]['latency']).toEqual('-');
+ expect(component.sensors[0]['throughput']).toEqual('-');
+
+ }));
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.ts
new file mode 100644
index 0000000..ccc8aca
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.component.ts
@@ -0,0 +1,368 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {Router, NavigationStart} from '@angular/router';
+import {SensorParserConfig} from '../../model/sensor-parser-config';
+import {SensorParserConfigService} from '../../service/sensor-parser-config.service';
+import {MetronAlerts} from '../../shared/metron-alerts';
+import {MetronDialogBox} from '../../shared/metron-dialog-box';
+import {Sort} from '../../util/enums';
+import {SortEvent} from '../../shared/metron-table/metron-table.directive';
+import {StormService} from '../../service/storm.service';
+import {TopologyStatus} from '../../model/topology-status';
+import {SensorParserConfigHistory} from '../../model/sensor-parser-config-history';
+import {SensorParserConfigHistoryService} from '../../service/sensor-parser-config-history.service';
+
+@Component({
+ selector: 'metron-config-sensor-parser-list',
+ templateUrl: 'sensor-parser-list.component.html',
+ styleUrls: ['sensor-parser-list.component.scss']
+})
+export class SensorParserListComponent implements OnInit {
+
+ componentName: string = 'Sensors';
+ @ViewChild('table') table;
+
+ count: number = 0;
+ sensors: SensorParserConfigHistory[] = [];
+ sensorsStatus: TopologyStatus[] = [];
+ selectedSensors: SensorParserConfigHistory[] = [];
+ enableAutoRefresh: boolean = true;
+
+ constructor(private sensorParserConfigService: SensorParserConfigService,
+ private sensorParserConfigHistoryService: SensorParserConfigHistoryService,
+ private stormService: StormService,
+ private router: Router,
+ private metronAlerts: MetronAlerts,
+ private metronDialogBox: MetronDialogBox) {
+ router.events.subscribe(event => {
+ if (event instanceof NavigationStart && event.url === '/sensors') {
+ this.onNavigationStart();
+ }
+ });
+ }
+
+ getSensors(justOnce: boolean) {
+ this.sensorParserConfigHistoryService.getAll().subscribe(
+ (results: SensorParserConfigHistory[]) => {
+ this.sensors = results;
+ this.selectedSensors = [];
+ this.count = this.sensors.length;
+ if (!justOnce) {
+ this.getStatus();
+ this.pollStatus();
+ } else {
+ this.getStatus();
+ }
+
+ }
+ );
+ }
+
+ onSort($event: SortEvent) {
+ switch ($event.sortBy) {
+ case 'sensorTopic':
+ this.sensors.sort((obj1: SensorParserConfigHistory, obj2: SensorParserConfigHistory) => {
+ if ($event.sortOrder === Sort.ASC) {
+ return obj1.config[$event.sortBy].localeCompare(obj2.config[$event.sortBy]);
+ }
+ return obj2.config[$event.sortBy].localeCompare(obj1.config[$event.sortBy]);
+ });
+ break;
+ case 'parserClassName':
+ this.sensors.sort((obj1: SensorParserConfigHistory, obj2: SensorParserConfigHistory) => {
+ if ($event.sortOrder === Sort.ASC) {
+ return this.getParserType(obj1.config).localeCompare(this.getParserType(obj2.config));
+ }
+ return this.getParserType(obj2.config).localeCompare(this.getParserType(obj1.config));
+ });
+ break;
+ case 'status':
+ case 'modifiedBy':
+ case 'modifiedByDate':
+ case 'latency':
+ case 'throughput':
+ this.sensors.sort((obj1: SensorParserConfigHistory, obj2: SensorParserConfigHistory) => {
+ if (!obj1[$event.sortBy] || !obj1[$event.sortBy]) {
+ return 0;
+ }
+ if ($event.sortOrder === Sort.ASC) {
+ return obj1[$event.sortBy].localeCompare(obj2[$event.sortBy]);
+ }
+
+ return obj2[$event.sortBy].localeCompare(obj1[$event.sortBy]);
+ });
+ break;
+ }
+ }
+
+ private pollStatus() {
+ this.stormService.pollGetAll().subscribe(
+ (results: TopologyStatus[]) => {
+ this.sensorsStatus = results;
+ this.updateSensorStatus();
+ },
+ error => {
+ this.updateSensorStatus();
+ }
+ );
+ }
+
+ private getStatus() {
+ this.stormService.getAll().subscribe(
+ (results: TopologyStatus[]) => {
+ this.sensorsStatus = results;
+ this.updateSensorStatus();
+ },
+ error => {
+ this.updateSensorStatus();
+ }
+ );
+ }
+
+ updateSensorStatus() {
+ for (let sensor of this.sensors) {
+
+ let status: TopologyStatus = this.sensorsStatus.find(status => {
+ return status.name === sensor.config.sensorTopic;
+ });
+
+ if (status) {
+ if (status.status === 'ACTIVE') {
+ sensor['status'] = 'Running';
+ }
+ if (status.status === 'KILLED') {
+ sensor['status'] = 'Stopped';
+ }
+ if (status.status === 'INACTIVE') {
+ sensor['status'] = 'Disabled';
+ }
+ } else {
+ sensor['status'] = 'Stopped';
+ }
+
+ sensor['latency'] = status && status.status === 'ACTIVE' ? (status.latency + 's') : '-';
+ sensor['throughput'] = status && status.status === 'ACTIVE' ? (Math.round(status.throughput * 100) / 100) + 'kb/s' : '-';
+ }
+ }
+
+ getParserType(sensor: SensorParserConfig): string {
+ let items = sensor.parserClassName.split('.');
+ return items[items.length - 1].replace('Basic', '').replace('Parser', '');
+ }
+
+ ngOnInit() {
+ this.getSensors(false);
+ this.sensorParserConfigService.dataChanged$.subscribe(
+ data => {
+ this.getSensors(true);
+ }
+ );
+ }
+
+ addAddSensor() {
+ this.router.navigateByUrl('/sensors(dialog:sensors-config/new)');
+ }
+
+ navigateToSensorEdit(selectedSensor: SensorParserConfig, event) {
+ this.sensorParserConfigService.setSeletedSensor(selectedSensor);
+ this.router.navigateByUrl('/sensors(dialog:sensors-config/' + selectedSensor.sensorTopic + ')');
+ event.stopPropagation();
+ }
+
+ onRowSelected(sensor: SensorParserConfigHistory, $event) {
+ if ($event.target.checked) {
+ this.selectedSensors.push(sensor);
+ } else {
+ this.selectedSensors.splice(this.selectedSensors.indexOf(sensor), 1);
+ }
+ }
+
+ onSelectDeselectAll($event) {
+ let checkBoxes = this.table.nativeElement.querySelectorAll('tr td:last-child input[type="checkbox"]');
+
+ for (let ele of checkBoxes) {
+ ele.checked = $event.target.checked;
+ }
+
+ if ($event.target.checked) {
+ this.selectedSensors = this.sensors.slice(0).map((sensorParserInfo: SensorParserConfigHistory) => sensorParserInfo);
+ } else {
+ this.selectedSensors = [];
+ }
+ }
+
+ onSensorRowSelect(sensor: SensorParserConfig, $event) {
+ if ($event.target.type !== 'checkbox' && $event.target.parentElement.firstChild.type !== 'checkbox') {
+
+ if (this.sensorParserConfigService.getSelectedSensor() === sensor) {
+ this.sensorParserConfigService.setSeletedSensor(null);
+ this.router.navigateByUrl('/sensors');
+ return;
+ }
+
+ this.sensorParserConfigService.setSeletedSensor(sensor);
+ this.router.navigateByUrl('/sensors(dialog:sensors-readonly/' + sensor.sensorTopic + ')');
+ }
+ }
+
+ deleteSensor($event, selectedSensorsToDelete: SensorParserConfig[]) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+
+ let sensorNames = selectedSensorsToDelete.map(sensor => { return sensor.sensorTopic; });
+ let confirmationsMsg = 'Are you sure you want to delete sensor(s) ' + sensorNames.join(', ') + ' ?';
+
+ this.metronDialogBox.showConfirmationMessage(confirmationsMsg).subscribe(result => {
+ if (result) {
+ this.sensorParserConfigService.deleteSensorParserConfigs(selectedSensorsToDelete)
+ .subscribe((deleteResult: {success: Array<string>, failure: Array<string>}) => {
+ if (deleteResult.success.length > 0) {
+ this.metronAlerts.showSuccessMessage('Deleted sensors: ' + deleteResult.success.join(', '));
+ }
+
+ if (deleteResult.failure.length > 0) {
+ this.metronAlerts.showErrorMessage('Unable to deleted sensors: ' + deleteResult.failure.join(', '));
+ }
+ });
+ }
+ });
+ }
+
+ onDeleteSensor() {
+ let selectedSensorsToDelete = this.selectedSensors.map(info => {
+ return info.config;
+ });
+ this.deleteSensor(null, selectedSensorsToDelete);
+ }
+
+ onStopSensors() {
+ for (let sensor of this.selectedSensors) {
+ if (sensor['status'] === 'Running' || sensor['status'] === 'Disabled') {
+ this.onStopSensor(sensor, null);
+ }
+ }
+ }
+
+ onStopSensor(sensor: SensorParserConfigHistory, event) {
+ this.toggleStartStopInProgress(sensor);
+
+ this.stormService.stopParser(sensor.config.sensorTopic).subscribe(result => {
+ this.metronAlerts.showSuccessMessage('Stopped sensor ' + sensor.config.sensorTopic);
+ this.toggleStartStopInProgress(sensor);
+ },
+ error => {
+ this.metronAlerts.showErrorMessage('Unable to stop sensor ' + sensor.config.sensorTopic);
+ this.toggleStartStopInProgress(sensor);
+ });
+
+ if (event !== null) {
+ event.stopPropagation();
+ }
+ }
+
+ onStartSensors() {
+ for (let sensor of this.selectedSensors) {
+ if (sensor['status'] === 'Stopped') {
+ this.onStartSensor(sensor, null);
+ }
+ }
+ }
+
+ onStartSensor(sensor: SensorParserConfigHistory, event) {
+ this.toggleStartStopInProgress(sensor);
+
+ this.stormService.startParser(sensor.config.sensorTopic).subscribe(result => {
+ if (result['status'] === 'ERROR') {
+ this.metronAlerts.showErrorMessage('Unable to start sensor ' + sensor.config.sensorTopic + ': ' + result['message']);
+ } else {
+ this.metronAlerts.showSuccessMessage('Started sensor ' + sensor.config.sensorTopic);
+ }
+
+ this.toggleStartStopInProgress(sensor);
+ },
+ error => {
+ this.metronAlerts.showErrorMessage('Unable to start sensor ' + sensor.config.sensorTopic);
+ this.toggleStartStopInProgress(sensor);
+ });
+
+ if (event !== null) {
+ event.stopPropagation();
+ }
+ }
+
+ onDisableSensors() {
+ for (let sensor of this.selectedSensors) {
+ if (sensor['status'] === 'Running') {
+ this.onDisableSensor(sensor, null);
+ }
+ }
+ }
+
+ onDisableSensor(sensor: SensorParserConfigHistory, event) {
+ this.toggleStartStopInProgress(sensor);
+
+ this.stormService.deactivateParser(sensor.config.sensorTopic).subscribe(result => {
+ this.metronAlerts.showSuccessMessage('Disabled sensor ' + sensor.config.sensorTopic);
+ this.toggleStartStopInProgress(sensor);
+ },
+ error => {
+ this.metronAlerts.showErrorMessage('Unable to disable sensor ' + sensor.config.sensorTopic);
+ this.toggleStartStopInProgress(sensor);
+ });
+
+ if (event !== null) {
+ event.stopPropagation();
+ }
+ }
+
+ onEnableSensors() {
+ for (let sensor of this.selectedSensors) {
+ if (sensor['status'] === 'Disabled') {
+ this.onEnableSensor(sensor, null);
+ }
+ }
+ }
+
+ onEnableSensor(sensor: SensorParserConfigHistory, event) {
+ this.toggleStartStopInProgress(sensor);
+
+ this.stormService.activateParser(sensor.config.sensorTopic).subscribe(result => {
+ this.metronAlerts.showSuccessMessage('Enabled sensor ' + sensor.config.sensorTopic);
+ this.toggleStartStopInProgress(sensor);
+ },
+ error => {
+ this.metronAlerts.showErrorMessage('Unable to enabled sensor ' + sensor.config.sensorTopic);
+ this.toggleStartStopInProgress(sensor);
+ });
+
+ if (event != null) {
+ event.stopPropagation();
+ }
+ }
+
+ toggleStartStopInProgress(sensor: SensorParserConfigHistory) {
+ sensor.config['startStopInProgress'] = !sensor.config['startStopInProgress'];
+ }
+
+ onNavigationStart() {
+ this.sensorParserConfigService.setSeletedSensor(null);
+ this.selectedSensors = [];
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.module.ts
new file mode 100644
index 0000000..d057623
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.module.ts
@@ -0,0 +1,30 @@
+/**
+ * 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 { NgModule } from '@angular/core';
+import {routing} from './sensor-parser-list.routing';
+import {SensorParserListComponent} from './sensor-parser-list.component';
+import {SharedModule} from '../../shared/shared.module';
+import {APP_CONFIG, METRON_REST_CONFIG} from '../../app.config';
+import {MetronTableModule} from '../../shared/metron-table/metron-table.module';
+
+@NgModule ({
+ imports: [ routing, SharedModule, MetronTableModule ],
+ declarations: [ SensorParserListComponent ],
+ providers: [{ provide: APP_CONFIG, useValue: METRON_REST_CONFIG }]
+})
+export class SensorParserListModule { }
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.routing.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.routing.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.routing.ts
new file mode 100644
index 0000000..4910aa5
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-list/sensor-parser-list.routing.ts
@@ -0,0 +1,25 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { ModuleWithProviders } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import {SensorParserListComponent} from './sensor-parser-list.component';
+import {AuthGuard} from '../../shared/auth-guard';
+
+export const routing: ModuleWithProviders = RouterModule.forChild([
+ { path: '', component: SensorParserListComponent, canActivate: [AuthGuard]}
+]);
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.html b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.html
new file mode 100644
index 0000000..291327f
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.html
@@ -0,0 +1,49 @@
+<!--
+ Licensed to the Apache Software
+ Foundation (ASF) under one or more contributor license agreements. See the
+ NOTICE file distributed with this work for additional information regarding
+ copyright ownership. The ASF licenses this file to You under the Apache License,
+ Version 2.0 (the "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the License for
+ the specific language governing permissions and limitations under the License.
+ -->
+<div class="metron-slider-pane-edit fill load-left-to-right dialog2x">
+
+ <div class="form-group">
+ <div class="form-title">Configure Raw JSON</div>
+ <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="onCancel()"></i>
+ </div>
+
+
+ <form role="form" class="enrichments-form">
+
+ <div class="form-group">
+ <label attr.for="newSensorParserConfig">SENSOR PARSER CONFIG</label>
+ <metron-config-ace-editor [(ngModel)]="newSensorParserConfig" [ngModelOptions]="{standalone: true}" [type]="'JSON'" [placeHolder]="'Enter Sensor Parser Config'"> </metron-config-ace-editor>
+ </div>
+ <div class="form-group">
+ <label attr.for="newSensorEnrichmentConfig">SENSOR ENRICHMENT CONFIG</label>
+ <metron-config-ace-editor [(ngModel)]="newSensorEnrichmentConfig" [ngModelOptions]="{standalone: true}" [type]="'JSON'" [placeHolder]="'Enter Sensor Enrichment Config'"> </metron-config-ace-editor>
+ </div>
+ <div class="form-group">
+ <label attr.for="newIndexingConfigurations">INDEXING CONFIGURATIONS</label>
+ <metron-config-ace-editor [(ngModel)]="newIndexingConfigurations" [ngModelOptions]="{standalone: true}" [type]="'JSON'" [placeHolder]="'Enter Indexing Configurations'"> </metron-config-ace-editor>
+ </div>
+
+ <div class="form-group">
+ <div class="form-seperator-edit"></div>
+ <div class="button-row">
+ <button type="submit" class="btn form-enable-disable-button" (click)="onSave()">SAVE</button>
+ <button class="btn form-enable-disable-button" (click)="onCancel()" >CANCEL</button>
+ </div>
+ </div>
+
+ </form>
+
+</div>
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.scss
new file mode 100644
index 0000000..3a9f5e3
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.scss
@@ -0,0 +1,36 @@
+/**
+ * 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.
+ */
+textarea
+{
+ height: auto;
+}
+
+.form-group
+{
+ padding-left: 25px;
+ padding-right: 20px;
+ padding-bottom: 10px;
+}
+
+.button-row {
+ padding-top: 10px;
+}
+
+.form-enable-disable-button {
+ width: 16%;
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.spec.ts
new file mode 100644
index 0000000..f680f10
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.spec.ts
@@ -0,0 +1,191 @@
+/**
+ * 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 {async, TestBed, ComponentFixture} from '@angular/core/testing';
+import {SensorRawJsonComponent} from './sensor-raw-json.component';
+import {SharedModule} from '../../shared/shared.module';
+import {SimpleChanges, SimpleChange} from '@angular/core';
+import {SensorParserConfig} from '../../model/sensor-parser-config';
+import {SensorEnrichmentConfig, EnrichmentConfig, ThreatIntelConfig} from '../../model/sensor-enrichment-config';
+import {SensorRawJsonModule} from './sensor-raw-json.module';
+import {IndexingConfigurations} from '../../model/sensor-indexing-config';
+import '../../rxjs-operators';
+
+describe('Component: SensorRawJsonComponent', () => {
+
+ let fixture: ComponentFixture<SensorRawJsonComponent>;
+ let component: SensorRawJsonComponent;
+ let sensorParserConfigString = '{"parserClassName":"org.apache.metron.parsers.bro.BasicBroParser","sensorTopic":"bro",' +
+ '"parserConfig": {},"fieldTransformations":[]}';
+ let sensorParserConfig: SensorParserConfig = new SensorParserConfig();
+ sensorParserConfig.sensorTopic = 'bro';
+ sensorParserConfig.parserClassName = 'org.apache.metron.parsers.bro.BasicBroParser';
+ sensorParserConfig.parserConfig = {};
+
+ let sensorParserConfigWithClassNameString = `{"parserClassName":"org.apache.metron.parsers.bro.BasicBroParser","sensorTopic":"bro",
+ "parserConfig": {},"fieldTransformations":[], "writerClassName": "org.example.writerClassName",
+ "errorWriterClassName": "org.example.errorWriterClassName",
+ "filterClassName": "org.example.filterClassName", "invalidWriterClassName": "org.example.invalidWriterClassName"}`;
+ let sensorParserConfigWithClassName = Object.assign(new SensorParserConfig(), sensorParserConfig);
+ sensorParserConfigWithClassName.writerClassName = 'org.example.writerClassName';
+ sensorParserConfigWithClassName.errorWriterClassName = 'org.example.errorWriterClassName';
+ sensorParserConfigWithClassName.filterClassName = 'org.example.filterClassName';
+ sensorParserConfigWithClassName.invalidWriterClassName = 'org.example.invalidWriterClassName';
+
+ let sensorEnrichmentConfigString = '{"enrichment" : {"fieldMap": ' +
+ '{"geo": ["ip_dst_addr", "ip_src_addr"],"host": ["host"]}},"threatIntel": {"fieldMap": {"hbaseThreatIntel":' +
+ ' ["ip_src_addr", "ip_dst_addr"]},"fieldToTypeMap": {"ip_src_addr" : ["malicious_ip"],"ip_dst_addr" : ["malicious_ip"]}}}';
+ let sensorEnrichmentConfig = new SensorEnrichmentConfig();
+ sensorEnrichmentConfig.enrichment = Object.assign(new EnrichmentConfig(), {
+ 'fieldMap': {
+ 'geo': ['ip_dst_addr', 'ip_src_addr'],
+ 'host': ['host']
+ }
+ });
+ sensorEnrichmentConfig.threatIntel = Object.assign(new ThreatIntelConfig(), {
+ 'fieldMap': {
+ 'hbaseThreatIntel': ['ip_src_addr', 'ip_dst_addr']
+ },
+ 'fieldToTypeMap': {
+ 'ip_src_addr' : ['malicious_ip'],
+ 'ip_dst_addr' : ['malicious_ip']
+ }
+ });
+
+ let sensorEnrichmentConfigWithConfigString = `{"configuration": "some-configuration",
+ "enrichment" : {"fieldMap": {"geo": ["ip_dst_addr", "ip_src_addr"],"host": ["host"]}},
+ "threatIntel": {"fieldMap": {"hbaseThreatIntel":["ip_src_addr", "ip_dst_addr"]},
+ "fieldToTypeMap": {"ip_src_addr" : ["malicious_ip"],"ip_dst_addr" : ["malicious_ip"]}}}`;
+ let sensorEnrichmentConfigWithConfig = Object.assign(new SensorEnrichmentConfig(), sensorEnrichmentConfig);
+ sensorEnrichmentConfigWithConfig.configuration = 'some-configuration';
+
+ let sensorIndexingConfigString = `{"hdfs": {"index": "bro","batchSize": 5,"enabled":true},
+ "elasticsearch": {"index": "bro","batchSize": 5,"enabled":true},
+ "solr": {"index": "bro","batchSize": 5,"enabled":true}}`;
+ let sensorIndexingConfig = new IndexingConfigurations();
+ sensorIndexingConfig.hdfs.index = 'bro';
+ sensorIndexingConfig.hdfs.batchSize = 5;
+ sensorIndexingConfig.hdfs.enabled = true;
+ sensorIndexingConfig.elasticsearch.index = 'bro';
+ sensorIndexingConfig.elasticsearch.batchSize = 5;
+ sensorIndexingConfig.elasticsearch.enabled = true;
+ sensorIndexingConfig.solr.index = 'bro';
+ sensorIndexingConfig.solr.batchSize = 5;
+ sensorIndexingConfig.solr.enabled = true;
+
+ let sensorIndexingConfigChangedString = `{"hdfs": {"index": "squid","batchSize": 1,"enabled":true},
+ "elasticsearch": {"index": "squid","batchSize": 1,"enabled":true},
+ "solr": {"index": "squid","batchSize": 1,"enabled":true}}`;
+ let sensorIndexingConfigChanged = new IndexingConfigurations();
+ sensorIndexingConfigChanged.hdfs.index = 'squid';
+ sensorIndexingConfigChanged.hdfs.batchSize = 1;
+ sensorIndexingConfigChanged.hdfs.enabled = true;
+ sensorIndexingConfigChanged.elasticsearch.index = 'squid';
+ sensorIndexingConfigChanged.elasticsearch.batchSize = 1;
+ sensorIndexingConfigChanged.elasticsearch.enabled = true;
+ sensorIndexingConfigChanged.solr.index = 'squid';
+ sensorIndexingConfigChanged.solr.batchSize = 1;
+ sensorIndexingConfigChanged.solr.enabled = true;
+
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [SharedModule, SensorRawJsonModule],
+ });
+
+ fixture = TestBed.createComponent(SensorRawJsonComponent);
+ component = fixture.componentInstance;
+ }));
+
+ it('should create an instance', () => {
+ expect(component).toBeDefined();
+ });
+
+ it('should create an instance', () => {
+ spyOn(component, 'init');
+ let changes: SimpleChanges = {'showRawJson': new SimpleChange(false, true)};
+
+ component.ngOnChanges(changes);
+ expect(component.init).toHaveBeenCalled();
+
+ changes = {'showRawJson': new SimpleChange(true, false)};
+ component.ngOnChanges(changes);
+ expect(component.init['calls'].count()).toEqual(1);
+
+ fixture.destroy();
+ });
+
+ it('should initialise the fields', () => {
+
+ component.init();
+ expect(component.newSensorParserConfig).toEqual(undefined);
+ expect(component.newSensorEnrichmentConfig).toEqual(undefined);
+ expect(component.newIndexingConfigurations).toEqual(undefined);
+
+ component.sensorParserConfig = sensorParserConfig;
+ component.sensorEnrichmentConfig = sensorEnrichmentConfig;
+ component.indexingConfigurations = sensorIndexingConfig;
+ component.init();
+ expect(component.newSensorParserConfig).toEqual(JSON.stringify(sensorParserConfig, null, '\t'));
+ expect(component.newSensorEnrichmentConfig).toEqual(JSON.stringify(sensorEnrichmentConfig, null, '\t'));
+ expect(component.newIndexingConfigurations).toEqual(JSON.stringify(sensorIndexingConfig, null, '\t'));
+
+ fixture.destroy();
+ });
+
+ it('should save the fields', () => {
+ spyOn(component.hideRawJson, 'emit');
+ spyOn(component.onRawJsonChanged, 'emit');
+ component.sensorParserConfig = new SensorParserConfig();
+ component.sensorEnrichmentConfig = new SensorEnrichmentConfig();
+ component.indexingConfigurations = new IndexingConfigurations();
+
+ component.newSensorParserConfig = sensorParserConfigString;
+ component.newSensorEnrichmentConfig = sensorEnrichmentConfigString;
+ component.newIndexingConfigurations = sensorIndexingConfigString;
+ component.onSave();
+ expect(component.sensorParserConfig).toEqual(sensorParserConfig);
+ expect(component.sensorEnrichmentConfig).toEqual(sensorEnrichmentConfig);
+ expect(component.indexingConfigurations).toEqual(sensorIndexingConfig);
+ expect(component.hideRawJson.emit).toHaveBeenCalled();
+ expect(component.onRawJsonChanged.emit).toHaveBeenCalled();
+
+
+ component.newSensorParserConfig = sensorParserConfigWithClassNameString;
+ component.newSensorEnrichmentConfig = sensorEnrichmentConfigWithConfigString;
+ component.newIndexingConfigurations = sensorIndexingConfigChangedString;
+ component.onSave();
+ expect(component.sensorParserConfig).toEqual(sensorParserConfigWithClassName);
+ expect(component.sensorEnrichmentConfig).toEqual(sensorEnrichmentConfigWithConfig);
+ expect(component.indexingConfigurations).toEqual(sensorIndexingConfigChanged);
+ expect(component.hideRawJson.emit['calls'].count()).toEqual(2);
+ expect(component.onRawJsonChanged.emit['calls'].count()).toEqual(2);
+
+ fixture.destroy();
+ });
+
+ it('should hide panel', () => {
+ spyOn(component.hideRawJson, 'emit');
+
+ component.onCancel();
+
+ expect(component.hideRawJson.emit).toHaveBeenCalled();
+
+ fixture.destroy();
+ });
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.ts
new file mode 100644
index 0000000..38d9e90
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.component.ts
@@ -0,0 +1,103 @@
+/**
+ * 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, EventEmitter, Output, OnChanges, SimpleChanges} from '@angular/core';
+import {SensorParserConfig} from '../../model/sensor-parser-config';
+import {SensorEnrichmentConfig, EnrichmentConfig, ThreatIntelConfig} from '../../model/sensor-enrichment-config';
+import {IndexingConfigurations, SensorIndexingConfig} from '../../model/sensor-indexing-config';
+
+declare var ace: any;
+
+@Component({
+ selector: 'metron-config-sensor-raw-json',
+ templateUrl: './sensor-raw-json.component.html',
+ styleUrls: ['./sensor-raw-json.component.scss']
+})
+
+export class SensorRawJsonComponent implements OnChanges {
+
+ @Input() showRawJson: boolean;
+ @Input() sensorParserConfig: SensorParserConfig;
+ @Input() sensorEnrichmentConfig: SensorEnrichmentConfig;
+ @Input() indexingConfigurations: IndexingConfigurations;
+
+ @Output() hideRawJson: EventEmitter<boolean> = new EventEmitter<boolean>();
+ @Output() onRawJsonChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
+
+ newSensorParserConfig: string;
+ newSensorEnrichmentConfig: string;
+ newIndexingConfigurations: string;
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['showRawJson'] && changes['showRawJson'].currentValue) {
+ this.init();
+ }
+ }
+
+ init(): void {
+ if (this.sensorParserConfig) {
+ this.newSensorParserConfig = JSON.stringify(this.sensorParserConfig, null, '\t');
+ }
+
+ if (this.sensorEnrichmentConfig) {
+ this.newSensorEnrichmentConfig = JSON.stringify(this.sensorEnrichmentConfig, null, '\t');
+ }
+
+ if (this.indexingConfigurations) {
+ this.newIndexingConfigurations = JSON.stringify(this.indexingConfigurations, null, '\t');
+ }
+ }
+
+ onSave() {
+ let newParsedSensorParserConfig = JSON.parse(this.newSensorParserConfig);
+ this.sensorParserConfig.sensorTopic = newParsedSensorParserConfig.sensorTopic;
+ this.sensorParserConfig.parserConfig = newParsedSensorParserConfig.parserConfig;
+ this.sensorParserConfig.parserClassName = newParsedSensorParserConfig.parserClassName;
+ this.sensorParserConfig.fieldTransformations = newParsedSensorParserConfig.fieldTransformations;
+
+ if (newParsedSensorParserConfig.writerClassName != null) {
+ this.sensorParserConfig.writerClassName = newParsedSensorParserConfig.writerClassName;
+ }
+ if (newParsedSensorParserConfig.errorWriterClassName != null) {
+ this.sensorParserConfig.errorWriterClassName = newParsedSensorParserConfig.errorWriterClassName;
+ }
+ if (newParsedSensorParserConfig.filterClassName != null) {
+ this.sensorParserConfig.filterClassName = newParsedSensorParserConfig.filterClassName;
+ }
+ if (newParsedSensorParserConfig.invalidWriterClassName != null) {
+ this.sensorParserConfig.invalidWriterClassName = newParsedSensorParserConfig.invalidWriterClassName;
+ }
+
+ let newParsedSensorEnrichmentConfig = JSON.parse(this.newSensorEnrichmentConfig);
+ this.sensorEnrichmentConfig.enrichment = Object.assign(new EnrichmentConfig(), newParsedSensorEnrichmentConfig.enrichment);
+ this.sensorEnrichmentConfig.threatIntel = Object.assign(new ThreatIntelConfig(), newParsedSensorEnrichmentConfig.threatIntel);
+ if (newParsedSensorEnrichmentConfig.configuration != null) {
+ this.sensorEnrichmentConfig.configuration = newParsedSensorEnrichmentConfig.configuration;
+ }
+
+ let newParsedIndexingConfigurations = JSON.parse(this.newIndexingConfigurations);
+ this.indexingConfigurations.hdfs = Object.assign(new SensorIndexingConfig(), newParsedIndexingConfigurations.hdfs);
+ this.indexingConfigurations.elasticsearch = Object.assign(new SensorIndexingConfig(), newParsedIndexingConfigurations.elasticsearch);
+ this.indexingConfigurations.solr = Object.assign(new SensorIndexingConfig(), newParsedIndexingConfigurations.solr);
+ this.hideRawJson.emit(true);
+ this.onRawJsonChanged.emit(true);
+ }
+
+ onCancel(): void {
+ this.hideRawJson.emit(true);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.module.ts
new file mode 100644
index 0000000..965eb73
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-raw-json/sensor-raw-json.module.ts
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { NgModule } from '@angular/core';
+import {SharedModule} from '../../shared/shared.module';
+import {SensorRawJsonComponent} from './sensor-raw-json.component';
+import {AceEditorModule} from '../../shared/ace-editor/ace-editor.module';
+
+@NgModule ({
+ imports: [ SharedModule, AceEditorModule ],
+ declarations: [ SensorRawJsonComponent ],
+ exports: [ SensorRawJsonComponent ]
+})
+export class SensorRawJsonModule {}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.html b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.html
new file mode 100644
index 0000000..1a1f9f0
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.html
@@ -0,0 +1,49 @@
+<!--
+ Licensed to the Apache Software
+ Foundation (ASF) under one or more contributor license agreements. See the
+ NOTICE file distributed with this work for additional information regarding
+ copyright ownership. The ASF licenses this file to You under the Apache License,
+ Version 2.0 (the "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the License for
+ the specific language governing permissions and limitations under the License.
+ -->
+<div class="metron-slider-pane-edit fill load-left-to-right dialog1x">
+
+ <div class="form-title">Edit Rule</div>
+ <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="onCancel()"></i>
+
+
+ <form role="form" class="threat-intel-form">
+ <div class="form-group">
+ <label attr.for="statement">NAME</label>
+ <input type="text" class="form-control" name="ruleName" [(ngModel)]="newRiskLevelRule.name">
+ </div>
+ <div class="form-group">
+ <label attr.for="statement">TEXT</label>
+ <textarea rows="30" class="form-control" name="ruleValue" [(ngModel)]="newRiskLevelRule.rule" style="height: inherit"></textarea>
+ </div>
+ </form>
+ <form class="form-inline">
+ <div class="form-group">
+ <label attr.for="statement" style="width: 100%">SCORE ADJUSTMENT</label>
+ <input class="score-slider" name="scoreSlider" type="range" min="0" max="100" [(ngModel)]="newRiskLevelRule.score" style="width: 72%; margin-right: 4px">
+ <div style="width: 25%; display: inline-block">
+ <metron-config-number-spinner name="scoreText" [(ngModel)]="newRiskLevelRule.score" [min]="0" [max]="100"> </metron-config-number-spinner>
+ </div>
+ </div>
+ </form>
+
+ <div class="form-group">
+ <div class="form-seperator-edit"></div>
+ <div class="button-row">
+ <button type="submit" class="btn form-enable-disable-button" (click)="onSave()">SAVE</button>
+ <button class="btn form-enable-disable-button" (click)="onCancel()" >CANCEL</button>
+ </div>
+ </div>
+</div>
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.scss
new file mode 100644
index 0000000..ca6c7d7
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.scss
@@ -0,0 +1,90 @@
+/**
+ * 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.
+ */
+$range-gradient-start: #AC9B5A;
+$range-gradient-end: #D8747C;
+.form-title
+{
+ padding-left: 25px;
+}
+
+.form-group
+{
+ padding-left: 25px;
+ padding-right: 20px;
+ padding-bottom: 10px;
+}
+
+.close-button
+{
+ padding-right: 20px;
+}
+
+.score-adjustment
+{
+ display: inline-block;
+}
+
+.button-row {
+ padding-top: 10px;
+}
+
+.form-enable-disable-button {
+ width: 32%;
+}
+
+input[type="range"] {
+ height: 4px;
+ -webkit-appearance: none;
+ -moz-apperance: none;
+ background: $range-gradient-start;
+ background: -webkit-linear-gradient(left, $range-gradient-start, $range-gradient-end);
+ background: -moz-linear-gradient(left, $range-gradient-start, $range-gradient-end);
+ background: -ms-linear-gradient(left, $range-gradient-start, $range-gradient-end);
+ background: -o-linear-gradient(left, $range-gradient-start, $range-gradient-end);
+ background: linear-gradient(to right, $range-gradient-start, $range-gradient-end);
+ border-radius: 4px;
+
+ &:focus
+ {
+ outline: none;
+ }
+}
+
+input[type="range"]::-webkit-slider-thumb{
+ -webkit-appearance:none;
+ -moz-apperance:none;
+ width:15px;
+ height:15px;
+ -webkit-border-radius:20px;
+ -moz-border-radius:20px;
+ -ms-border-radius:20px;
+ -o-border-radius:20px;
+ border-radius:20px;
+ background-color: $range-gradient-start;
+}
+
+input[type="range"]::-moz-range-track
+{
+ background:transparent; border:0px;
+}
+
+input[type=range]::-moz-range-thumb
+{
+ background-color: $range-gradient-start;
+}
+
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.spec.ts
new file mode 100644
index 0000000..2f8cd24
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.spec.ts
@@ -0,0 +1,74 @@
+/**
+ * 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 {async, TestBed, ComponentFixture} from '@angular/core/testing';
+import {SensorRuleEditorComponent} from './sensor-rule-editor.component';
+import {SharedModule} from '../../../shared/shared.module';
+import {NumberSpinnerComponent} from '../../../shared/number-spinner/number-spinner.component';
+import {RiskLevelRule} from '../../../model/risk-level-rule';
+
+describe('Component: SensorRuleEditorComponent', () => {
+
+ let fixture: ComponentFixture<SensorRuleEditorComponent>;
+ let component: SensorRuleEditorComponent;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [SharedModule
+ ],
+ declarations: [ SensorRuleEditorComponent, NumberSpinnerComponent ],
+ providers: [
+ SensorRuleEditorComponent
+ ]
+ });
+
+ fixture = TestBed.createComponent(SensorRuleEditorComponent);
+ component = fixture.componentInstance;
+ }));
+
+ it('should create an instance', () => {
+ expect(component).toBeDefined();
+ });
+
+ it('should edit rules', async(() => {
+ let numCancelled = 0;
+ let savedRule = new RiskLevelRule();
+ component.onCancelTextEditor.subscribe((cancelled: boolean) => {
+ numCancelled++;
+ });
+ component.onSubmitTextEditor.subscribe((rule: RiskLevelRule) => {
+ savedRule = rule;
+ });
+
+ component.riskLevelRule = {name: 'rule1', rule: 'initial rule', score: 1, comment: ''};
+ component.ngOnInit();
+ component.onSave();
+ let rule1 = Object.assign(new RiskLevelRule(), {name: 'rule1', rule: 'initial rule', score: 1, comment: ''});
+ expect(savedRule).toEqual(rule1);
+
+ component.riskLevelRule = {name: 'rule2', rule: 'new rule', score: 2, comment: ''};
+ component.ngOnInit();
+ component.onSave();
+ let rule2 = Object.assign(new RiskLevelRule(), {name: 'rule2', rule: 'new rule', score: 2, comment: ''});
+ expect(savedRule).toEqual(rule2);
+
+ expect(numCancelled).toEqual(0);
+ component.onCancel();
+ expect(numCancelled).toEqual(1);
+ }));
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.ts
new file mode 100644
index 0000000..1bdfea1
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.component.ts
@@ -0,0 +1,49 @@
+/**
+ * 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, EventEmitter, Output, OnInit} from '@angular/core';
+import {RiskLevelRule} from '../../../model/risk-level-rule';
+
+@Component({
+ selector: 'metron-config-sensor-rule-editor',
+ templateUrl: './sensor-rule-editor.component.html',
+ styleUrls: ['./sensor-rule-editor.component.scss']
+})
+
+export class SensorRuleEditorComponent implements OnInit {
+
+ @Input() riskLevelRule: RiskLevelRule;
+
+ @Output() onCancelTextEditor: EventEmitter<boolean> = new EventEmitter<boolean>();
+ @Output() onSubmitTextEditor: EventEmitter<RiskLevelRule> = new EventEmitter<RiskLevelRule>();
+ newRiskLevelRule = new RiskLevelRule();
+
+ constructor() { }
+
+ ngOnInit() {
+ Object.assign(this.newRiskLevelRule, this.riskLevelRule);
+ }
+
+ onSave(): void {
+ this.onSubmitTextEditor.emit(this.newRiskLevelRule);
+ }
+
+ onCancel(): void {
+ this.onCancelTextEditor.emit(true);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.module.ts
new file mode 100644
index 0000000..a99c8cf
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/rule-editor/sensor-rule-editor.module.ts
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { NgModule } from '@angular/core';
+import {SharedModule} from '../../../shared/shared.module';
+import {SensorRuleEditorComponent} from './sensor-rule-editor.component';
+import {NumberSpinnerModule} from '../../../shared/number-spinner/number-spinner.module';
+
+@NgModule ({
+ imports: [ SharedModule, NumberSpinnerModule ],
+ declarations: [ SensorRuleEditorComponent ],
+ exports: [ SensorRuleEditorComponent ]
+})
+export class SensorRuleEditorModule {}