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:19 UTC
[08/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-config-readonly/sensor-parser-config-readonly.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts
new file mode 100644
index 0000000..dbbec12
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts
@@ -0,0 +1,708 @@
+/**
+ * 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 {Observable} from 'rxjs/Observable';
+import {Router, ActivatedRoute, Params} from '@angular/router';
+import {Inject} from '@angular/core';
+import {SensorParserConfigHistory} from '../../model/sensor-parser-config-history';
+import {RequestOptions, Response, ResponseOptions, Http} from '@angular/http';
+import {SensorParserConfigReadonlyComponent} from './sensor-parser-config-readonly.component';
+import {SensorParserConfigService} from '../../service/sensor-parser-config.service';
+import {KafkaService} from '../../service/kafka.service';
+import {TopologyStatus} from '../../model/topology-status';
+import {SensorParserConfig} from '../../model/sensor-parser-config';
+import {KafkaTopic} from '../../model/kafka-topic';
+import {AuthenticationService} from '../../service/authentication.service';
+import {SensorParserConfigHistoryService} from '../../service/sensor-parser-config-history.service';
+import {StormService} from '../../service/storm.service';
+import {MetronAlerts} from '../../shared/metron-alerts';
+import {FieldTransformer} from '../../model/field-transformer';
+import {SensorParserConfigReadonlyModule} from './sensor-parser-config-readonly.module';
+import {APP_CONFIG, METRON_REST_CONFIG} from '../../app.config';
+import {IAppConfig} from '../../app.config.interface';
+import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service';
+import {SensorEnrichmentConfig, EnrichmentConfig, ThreatIntelConfig} from '../../model/sensor-enrichment-config';
+import {HdfsService} from '../../service/hdfs.service';
+import {GrokValidationService} from '../../service/grok-validation.service';
+
+class MockRouter {
+
+ navigateByUrl(url: string) {
+
+ }
+
+}
+
+class MockActivatedRoute {
+ private name: string;
+ params: Observable<Params>;
+
+ setNameForTest(name: string) {
+ this.name = name;
+ this.params = Observable.create(observer => {
+ observer.next({id: this.name});
+ observer.complete();
+ });
+ }
+}
+
+class MockAuthenticationService extends AuthenticationService {
+
+ constructor(private http2: Http, private router2: Router, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, router2, config2);
+ }
+
+ public getCurrentUser(options: RequestOptions): Observable<Response> {
+ let responseOptions: ResponseOptions = new ResponseOptions();
+ responseOptions.body = 'user';
+ let response: Response = new Response(responseOptions);
+ return Observable.create(observer => {
+ observer.next(response);
+ observer.complete();
+ });
+ };
+}
+
+class MockSensorParserConfigHistoryService extends SensorParserConfigHistoryService {
+
+ private sensorParserConfigHistory: SensorParserConfigHistory;
+
+ constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, config2);
+ }
+
+ public setForTest(sensorParserConfigHistory: SensorParserConfigHistory) {
+ this.sensorParserConfigHistory = sensorParserConfigHistory;
+ }
+
+ public get(name: string): Observable<SensorParserConfigHistory> {
+ return Observable.create(observer => {
+ observer.next(this.sensorParserConfigHistory);
+ observer.complete();
+ });
+ }
+}
+
+class MockSensorParserConfigService extends SensorParserConfigService {
+
+ constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, config2);
+ }
+
+}
+
+class MockStormService extends StormService {
+ private topologyStatus: TopologyStatus;
+
+ constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, config2);
+ }
+
+ public setForTest(topologyStatus: TopologyStatus) {
+ this.topologyStatus = topologyStatus;
+ }
+
+ public getStatus(name: string): Observable<TopologyStatus> {
+ return Observable.create(observer => {
+ observer.next(this.topologyStatus);
+ observer.complete();
+ });
+ }
+}
+
+class MockGrokValidationService extends GrokValidationService {
+
+ constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, config2);
+ }
+
+ public list(): Observable<string[]> {
+ return Observable.create(observer => {
+ observer.next({
+ 'BASE10NUM': '(?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+)))',
+ 'BASE16FLOAT': '\\b(?<![0-9A-Fa-f.])(?:[+-]?(?:0x)?(?:(?:[0-9A-Fa-f]+(?:\\.[0-9A-Fa-f]*)?)|(?:\\.[0-9A-Fa-f]+)))\\b',
+ 'BASE16NUM': '(?<![0-9A-Fa-f])(?:[+-]?(?:0x)?(?:[0-9A-Fa-f]+))',
+ 'CISCOMAC': '(?:(?:[A-Fa-f0-9]{4}\\.){2}[A-Fa-f0-9]{4})',
+ 'COMMONMAC': '(?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})',
+ 'DATA': '.*?'
+ });
+ observer.complete();
+ });
+ }
+}
+
+class MockKafkaService extends KafkaService {
+
+ private kafkaTopic: KafkaTopic;
+
+ constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, config2);
+ }
+
+ public setForTest(kafkaTopic: KafkaTopic) {
+ this.kafkaTopic = kafkaTopic;
+ }
+
+ public get(name: string): Observable<KafkaTopic> {
+ return Observable.create(observer => {
+ observer.next(this.kafkaTopic);
+ observer.complete();
+ });
+ }
+
+ public sample(name: string): Observable<string> {
+ return Observable.create(observer => {
+ observer.next(JSON.stringify({'data': 'data1', 'data2': 'data3'}));
+ observer.complete();
+ });
+ }
+}
+
+class MockHdfsService extends HdfsService {
+ private fileList: string[];
+ private contents: string;
+
+ constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) {
+ super(http2, config2);
+ }
+
+ public setContents(contents: string) {
+ this.contents = contents;
+ }
+
+ public list(path: string): Observable<string[]> {
+ if (this.fileList === null) {
+ return Observable.throw('Error');
+ }
+ return Observable.create(observer => {
+ observer.next(this.fileList);
+ observer.complete();
+ });
+ }
+
+ public read(path: string): Observable<string> {
+ if (this.contents === null) {
+ return Observable.throw('Error');
+ }
+ return Observable.create(observer => {
+ observer.next(this.contents);
+ observer.complete();
+ });
+ }
+
+ public post(path: string, contents: string): Observable<Response> {
+ return Observable.create(observer => {
+ observer.next({});
+ observer.complete();
+ });
+ }
+
+ public deleteFile(path: string): Observable<Response> {
+ return Observable.create(observer => {
+ observer.next({});
+ observer.complete();
+ });
+ }
+}
+
+class MockSensorEnrichmentConfigService {
+ private sensorEnrichmentConfig: SensorEnrichmentConfig;
+
+ setForTest(sensorEnrichmentConfig: SensorEnrichmentConfig) {
+ this.sensorEnrichmentConfig = sensorEnrichmentConfig;
+ }
+
+ public get(name: string): Observable<SensorEnrichmentConfig> {
+ return Observable.create(observer => {
+ observer.next(this.sensorEnrichmentConfig);
+ observer.complete();
+ });
+ }
+
+ public getAvailable(): Observable<string[]> {
+ return Observable.create((observer) => {
+ observer.next(['geo', 'host', 'whois']);
+ observer.complete();
+ });
+ }
+}
+
+describe('Component: SensorParserConfigReadonly', () => {
+
+ let component: SensorParserConfigReadonlyComponent;
+ let fixture: ComponentFixture<SensorParserConfigReadonlyComponent>;
+ let sensorParserConfigHistoryService: MockSensorParserConfigHistoryService;
+ let sensorEnrichmentConfigService: MockSensorEnrichmentConfigService;
+ let sensorParserConfigService: SensorParserConfigService;
+ let kafkaService: MockKafkaService;
+ let hdfsService: MockHdfsService;
+ let grokValidationService: MockGrokValidationService;
+ let stormService: MockStormService;
+ let alerts: MetronAlerts;
+ let authenticationService: AuthenticationService;
+ let router: MockRouter;
+ let activatedRoute: MockActivatedRoute;
+
+ beforeEach(async(() => {
+
+ TestBed.configureTestingModule({
+ imports: [SensorParserConfigReadonlyModule],
+ providers: [
+ {provide: Http},
+ {provide: ActivatedRoute, useClass: MockActivatedRoute},
+ {provide: AuthenticationService, useClass: MockAuthenticationService},
+ {provide: SensorEnrichmentConfigService, useClass: MockSensorEnrichmentConfigService},
+ {provide: SensorParserConfigHistoryService, useClass: MockSensorParserConfigHistoryService},
+ {provide: SensorParserConfigService, useClass: MockSensorParserConfigService},
+ {provide: StormService, useClass: MockStormService},
+ {provide: KafkaService, useClass: MockKafkaService},
+ {provide: HdfsService, useClass: MockHdfsService},
+ {provide: GrokValidationService, useClass: MockGrokValidationService},
+ {provide: Router, useClass: MockRouter},
+ {provide: APP_CONFIG, useValue: METRON_REST_CONFIG},
+ MetronAlerts
+ ]
+ }).compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(SensorParserConfigReadonlyComponent);
+ component = fixture.componentInstance;
+ activatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
+ hdfsService = fixture.debugElement.injector.get(HdfsService);
+ authenticationService = fixture.debugElement.injector.get(AuthenticationService);
+ sensorParserConfigHistoryService = fixture.debugElement.injector.get(SensorParserConfigHistoryService);
+ sensorEnrichmentConfigService = fixture.debugElement.injector.get(SensorEnrichmentConfigService);
+ sensorParserConfigService = fixture.debugElement.injector.get(SensorParserConfigService);
+ stormService = fixture.debugElement.injector.get(StormService);
+ kafkaService = fixture.debugElement.injector.get(KafkaService);
+ grokValidationService = fixture.debugElement.injector.get(GrokValidationService);
+ router = fixture.debugElement.injector.get(Router);
+ alerts = fixture.debugElement.injector.get(MetronAlerts);
+ });
+
+ }));
+
+ it('should create an instance', async(() => {
+ expect(component).toBeDefined();
+ }));
+
+ it('should have metadata defined ', async(() => {
+ expect(component.editViewMetaData.length).toEqual(24);
+ }));
+
+ it('should have sensorsService with parserName and grokPattern defined and kafkaService defined', async(() => {
+ let sensorParserInfo = new SensorParserConfigHistory();
+ let sensorParserConfig = new SensorParserConfig();
+ let kafkaTopic = new KafkaTopic();
+ let topologyStatus = new TopologyStatus();
+
+ sensorParserConfig.sensorTopic = 'bro';
+ sensorParserConfig.parserClassName = 'org.apache.metron.parsers.GrokParser';
+ sensorParserConfig.parserConfig = {grokPattern: 'SQUID_DELIMITED squid grok statement'};
+ sensorParserInfo.config = sensorParserConfig;
+
+ kafkaTopic.name = 'bro';
+ kafkaTopic.numPartitions = 1;
+ kafkaTopic.replicationFactor = 1;
+
+ topologyStatus.name = 'bro';
+ topologyStatus.latency = 10.1;
+ topologyStatus.throughput = 15.2;
+
+ let broEnrichment = {
+ 'fieldMap': {
+ 'geo': ['ip_dst_addr'],
+ 'host': ['ip_dst_addr'],
+ 'whois': [],
+ 'stellar': {'config': {'group1': {}}}
+ },
+ 'fieldToTypeMap': {}, 'config': {}
+ };
+ let broThreatIntel = {'threatIntel': {
+ 'fieldMap': { 'hbaseThreatIntel': ['ip_dst_addr'] },
+ 'fieldToTypeMap': { 'ip_dst_addr': ['malicious_ip'] }
+ }
+ };
+ let broEnrichments = new SensorEnrichmentConfig();
+ broEnrichments.enrichment = Object.assign(new EnrichmentConfig(), broEnrichment);
+ broEnrichments.threatIntel = Object.assign(new ThreatIntelConfig(), broThreatIntel);
+
+ sensorEnrichmentConfigService.setForTest(broEnrichments);
+ sensorParserConfigHistoryService.setForTest(sensorParserInfo);
+ kafkaService.setForTest(kafkaTopic);
+ stormService.setForTest(topologyStatus);
+
+ activatedRoute.setNameForTest('bro');
+
+ component.ngOnInit();
+ expect(component.startStopInProgress).toEqual(false);
+ expect(component.sensorParserConfigHistory).toEqual(Object.assign(new SensorParserConfigHistory(), sensorParserInfo));
+ expect(component.kafkaTopic).toEqual(kafkaTopic);
+ expect(component.sensorEnrichmentConfig).toEqual(broEnrichments);
+ }));
+
+ it('getSensorStatusService should initialise the state variable to appropriate values ', async(() => {
+ let sensorParserStatus = new TopologyStatus();
+ sensorParserStatus.name = 'bro';
+ sensorParserStatus.latency = 10.1;
+ sensorParserStatus.status = null;
+ sensorParserStatus.throughput = 15.2;
+
+ stormService.setForTest(sensorParserStatus);
+
+ component.getSensorStatusService();
+ expect(component.getTopologyStatus('status')).toEqual('Stopped');
+ expect(component.getTopologyStatus('sensorStatus')).toEqual('-');
+
+ sensorParserStatus.status = 'ACTIVE';
+ component.getSensorStatusService();
+ stormService.setForTest(sensorParserStatus);
+ expect(component.getTopologyStatus('status')).toEqual('Running');
+ expect(component.getTopologyStatus('sensorStatus')).toEqual('Enabled');
+
+ sensorParserStatus.status = 'KILLED';
+ component.getSensorStatusService();
+ stormService.setForTest(sensorParserStatus);
+ expect(component.getTopologyStatus('status')).toEqual('Stopped');
+ expect(component.getTopologyStatus('sensorStatus')).toEqual('-');
+
+ sensorParserStatus.status = 'INACTIVE';
+ component.getSensorStatusService();
+ stormService.setForTest(sensorParserStatus);
+ expect(component.getTopologyStatus('status')).toEqual('Disabled');
+ expect(component.getTopologyStatus('sensorStatus')).toEqual('Disabled');
+ }));
+
+ it('setGrokStatement should set the variables appropriately ', async(() => {
+ let grokStatement = 'SQUID_DELIMITED squid grok statement';
+ hdfsService.setContents(grokStatement);
+ let sensorParserInfo = new SensorParserConfigHistory();
+ let sensorParserConfig = new SensorParserConfig();
+ sensorParserConfig.parserConfig = {};
+
+ sensorParserConfig.parserConfig['grokPath'] = '/squid/grok/path';
+ sensorParserInfo.config = sensorParserConfig;
+
+ component.sensorParserConfigHistory = sensorParserInfo;
+ component.setGrokStatement();
+
+ expect(component.grokStatement).toEqual(grokStatement);
+ }));
+
+ it('setTransformsConfigKeys/getTransformsOutput should return the keys of the transforms config ', async(() => {
+ let sensorParserInfo = new SensorParserConfigHistory();
+ let sensorParserConfig = new SensorParserConfig();
+ let fieldTransformer1 = new FieldTransformer();
+ let fieldTransformer2 = new FieldTransformer();
+
+ fieldTransformer1.config = {'a': 'abc', 'x': 'xyz'};
+ fieldTransformer1.output = ['a', 'b', 'c'];
+ fieldTransformer2.config = {'x': 'klm', 'b': 'def'};
+ fieldTransformer2.output = ['a', 'b', 'c'];
+ sensorParserConfig.fieldTransformations = [fieldTransformer1, fieldTransformer2];
+ sensorParserInfo.config = sensorParserConfig;
+
+ component.setTransformsConfigKeys();
+ let transformsOutput = component.getTransformsOutput();
+
+ expect(component.transformsConfigKeys.length).toEqual(0);
+ expect(component.transformsConfigKeys).toEqual([]);
+ expect(component.transformsConfigMap).toEqual({});
+ expect(transformsOutput).toEqual('-');
+
+ component.sensorParserConfigHistory = sensorParserInfo;
+ component.setTransformsConfigKeys();
+ transformsOutput = component.getTransformsOutput();
+
+ expect(component.transformsConfigKeys.length).toEqual(3);
+ expect(component.transformsConfigKeys).toEqual(['a', 'b', 'x']);
+ expect(component.transformsConfigMap).toEqual({'a': ['abc'], 'b': ['def'], 'x': ['xyz', 'klm']});
+ expect(transformsOutput).toEqual('a, b, c');
+ }));
+
+ it('goBack should navigate to sensors page', async(() => {
+ router.navigateByUrl = jasmine.createSpy('navigateByUrl');
+
+ component.goBack();
+
+ expect(router.navigateByUrl).toHaveBeenCalledWith('/sensors');
+ }));
+
+ it('onEditSensor should navigate to sensor edit', async(() => {
+ router.navigateByUrl = jasmine.createSpy('navigateByUrl');
+
+ component.selectedSensorName = 'abc';
+
+ component.onEditSensor();
+ expect(router.navigateByUrl).toHaveBeenCalledWith('/sensors(dialog:sensors-config/abc)');
+ }));
+
+ it('should set sensorEnrichmentConfig and aggregationConfigKeys to be initialised', async(() => {
+ let threatIntel = {
+ 'fieldMap': {
+ 'hbaseThreatIntel': [ 'ip_dst_addr', 'ip_src_addr', 'action']
+ },
+ 'fieldToTypeMap': {
+ 'ip_dst_addr': [ 'malicious_ip'], 'ip_src_addr': [ 'malicious_ip'], 'action': [ 'malicious_ip']
+ },
+ 'config': {},
+ 'triageConfig': {
+ 'riskLevelRules': [
+ {
+ 'rule': 'IN_SUBNET(ip_dst_addr, \'192.168.0.0/24\')',
+ 'score': 3
+ },
+ {
+ 'rule': 'user.type in [ \'admin\', \'power\' ] and asset.type == \'web\'',
+ 'score': 3
+ },
+ ],
+ 'aggregator': 'MAX',
+ 'aggregationConfig': {}
+ }
+ };
+ let expected = [{'rule': 'IN_SUBNET(ip_dst_addr, \'192.168.0.0/24\')', 'score': 3},
+ {'rule': 'user.type in [ \'admin\', \'power\' ] and asset.type == \'web\'', 'score': 3}];
+
+ let sensorEnrichmentConfig = new SensorEnrichmentConfig();
+ sensorEnrichmentConfig.threatIntel = Object.assign(new ThreatIntelConfig(), threatIntel);
+ sensorEnrichmentConfigService.setForTest(sensorEnrichmentConfig);
+
+ component.getEnrichmentData();
+
+
+ expect(component.sensorEnrichmentConfig).toEqual(sensorEnrichmentConfig);
+ expect(component.rules).toEqual(expected);
+ }));
+
+ let setDataForSensorOperation = function () {
+ let sensorParserInfo = new SensorParserConfigHistory();
+ let sensorParserConfig = new SensorParserConfig();
+ let kafkaTopic = new KafkaTopic();
+ let topologyStatus = new TopologyStatus();
+
+ sensorParserConfig.sensorTopic = 'bro';
+ sensorParserConfig.parserClassName = 'org.apache.metron.parsers.GrokParser';
+ sensorParserConfig.parserConfig = {grokPattern: 'SQUID_DELIMITED squid grok statement'};
+ sensorParserInfo.config = sensorParserConfig;
+
+ kafkaTopic.name = 'bro';
+ kafkaTopic.numPartitions = 1;
+ kafkaTopic.replicationFactor = 1;
+
+ topologyStatus.name = 'bro';
+ topologyStatus.latency = 10.1;
+ topologyStatus.throughput = 15.2;
+
+ let broEnrichment = {
+ 'fieldMap': {
+ 'geo': ['ip_dst_addr'],
+ 'host': ['ip_dst_addr'],
+ 'whois': [],
+ 'stellar': {'config': {'group1': {}}}
+ },
+ 'fieldToTypeMap': {}, 'config': {}
+ };
+ let broThreatIntel = {'threatIntel': {
+ 'fieldMap': { 'hbaseThreatIntel': ['ip_dst_addr'] },
+ 'fieldToTypeMap': { 'ip_dst_addr': ['malicious_ip'] }
+ }
+ };
+ let broEnrichments = new SensorEnrichmentConfig();
+ broEnrichments.enrichment = Object.assign(new EnrichmentConfig(), broEnrichment);
+ broEnrichments.threatIntel = Object.assign(new ThreatIntelConfig(), broThreatIntel);
+
+ kafkaService.setForTest(kafkaTopic);
+ stormService.setForTest(topologyStatus);
+ sensorEnrichmentConfigService.setForTest(broEnrichments);
+ sensorParserConfigHistoryService.setForTest(sensorParserInfo);
+ };
+
+ it('onStartSensor should start sensor', async(() => {
+ spyOn(stormService, 'startParser').and.returnValue(Observable.create(observer => {
+ observer.next({});
+ observer.complete();
+ }));
+
+ alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage');
+ setDataForSensorOperation();
+
+ component.selectedSensorName = 'abc';
+
+ component.onStartSensor();
+
+ expect(stormService.startParser).toHaveBeenCalledWith('abc');
+ expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Started sensor abc');
+ }));
+
+ it('onStopSensor should stop the sensor', async(() => {
+ spyOn(stormService, 'stopParser').and.returnValue(Observable.create(observer => {
+ observer.next({});
+ observer.complete();
+ }));
+
+ alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage');
+ setDataForSensorOperation();
+
+ component.selectedSensorName = 'abc';
+
+ component.onStopSensor();
+
+ expect(stormService.stopParser).toHaveBeenCalledWith('abc');
+ expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Stopped sensor abc');
+ }));
+
+ it('onEnableSensor should enable sensor', async(() => {
+ spyOn(stormService, 'activateParser').and.returnValue(Observable.create(observer => {
+ observer.next({});
+ observer.complete();
+ }));
+
+ alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage');
+ setDataForSensorOperation();
+
+ component.selectedSensorName = 'abc';
+
+ component.onEnableSensor();
+
+ expect(stormService.activateParser).toHaveBeenCalledWith('abc');
+ expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Enabled sensor abc');
+ }));
+
+ it('onDisableSensor should disable the sensor', async(() => {
+ spyOn(stormService, 'deactivateParser').and.returnValue(Observable.create(observer => {
+ observer.next({});
+ observer.complete();
+ }));
+
+ alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage');
+ setDataForSensorOperation();
+
+ component.selectedSensorName = 'abc';
+
+ component.onDisableSensor();
+
+ expect(stormService.deactivateParser).toHaveBeenCalledWith('abc');
+ expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Disabled sensor abc');
+ }));
+
+ it('onDeleteSensor should delete the sensor', async(() => {
+ spyOn(sensorParserConfigService, 'deleteSensorParserConfig').and.returnValue(Observable.create(observer => {
+ observer.next({});
+ observer.complete();
+ }));
+
+ alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage');
+ router.navigateByUrl = jasmine.createSpy('navigateByUrl');
+ setDataForSensorOperation();
+
+ component.selectedSensorName = 'abc';
+
+ component.onDeleteSensor();
+
+ expect(sensorParserConfigService.deleteSensorParserConfig).toHaveBeenCalledWith('abc');
+ expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Deleted sensor abc');
+ expect(router.navigateByUrl).toHaveBeenCalledWith('/sensors');
+ }));
+
+ it('toggleStartStopInProgress should toggle the variable for showing progressbar', async(() => {
+ expect(component.startStopInProgress).toEqual(false);
+
+ component.startStopInProgress = true;
+ expect(component.startStopInProgress).toEqual(true);
+
+ component.startStopInProgress = false;
+ expect(component.startStopInProgress).toEqual(false);
+ }));
+
+ it('should toggleTransformLink', async(() => {
+ expect(component.transformLinkText).toEqual('show more');
+
+ component.toggleTransformLink();
+ expect(component.transformLinkText).toEqual('show less');
+
+ component.toggleTransformLink();
+ expect(component.transformLinkText).toEqual('show more');
+ }));
+
+ it('should toggleThreatTriageLink', async(() => {
+ expect(component.threatTriageLinkText).toEqual('show more');
+
+ component.toggleThreatTriageLink();
+ expect(component.threatTriageLinkText).toEqual('show less');
+
+ component.toggleThreatTriageLink();
+ expect(component.threatTriageLinkText).toEqual('show more');
+ }));
+
+ it('should hide start', async(() => {
+ component.topologyStatus.status = 'ACTIVE';
+ expect(component.isStartHidden()).toEqual(true);
+
+ component.topologyStatus.status = 'INACTIVE';
+ expect(component.isStartHidden()).toEqual(true);
+
+ component.topologyStatus.status = 'Stopped';
+ expect(component.isStartHidden()).toEqual(false);
+
+ component.topologyStatus.status = 'KILLED';
+ expect(component.isStartHidden()).toEqual(false);
+ }));
+
+ it('should hide stop', async(() => {
+ component.topologyStatus.status = 'ACTIVE';
+ expect(component.isStopHidden()).toEqual(false);
+
+ component.topologyStatus.status = 'INACTIVE';
+ expect(component.isStopHidden()).toEqual(false);
+
+ component.topologyStatus.status = 'Stopped';
+ expect(component.isStopHidden()).toEqual(true);
+
+ component.topologyStatus.status = 'KILLED';
+ expect(component.isStopHidden()).toEqual(true);
+ }));
+
+ it('should hide enable', async(() => {
+ component.topologyStatus.status = 'ACTIVE';
+ expect(component.isEnableHidden()).toEqual(true);
+
+ component.topologyStatus.status = 'INACTIVE';
+ expect(component.isEnableHidden()).toEqual(false);
+
+ component.topologyStatus.status = 'Stopped';
+ expect(component.isEnableHidden()).toEqual(true);
+
+ component.topologyStatus.status = 'KILLED';
+ expect(component.isEnableHidden()).toEqual(true);
+ }));
+
+ it('should hide disable', async(() => {
+ component.topologyStatus.status = 'ACTIVE';
+ expect(component.isDisableHidden()).toEqual(false);
+
+ component.topologyStatus.status = 'INACTIVE';
+ expect(component.isDisableHidden()).toEqual(true);
+
+ component.topologyStatus.status = 'Stopped';
+ expect(component.isDisableHidden()).toEqual(true);
+
+ component.topologyStatus.status = 'KILLED';
+ expect(component.isDisableHidden()).toEqual(true);
+ }));
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.ts
new file mode 100644
index 0000000..7edc2c5
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.ts
@@ -0,0 +1,372 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component, OnInit} from '@angular/core';
+import {KafkaService} from '../../service/kafka.service';
+import {Router, ActivatedRoute} from '@angular/router';
+import {KafkaTopic} from '../../model/kafka-topic';
+import {MetronAlerts} from '../../shared/metron-alerts';
+import {SensorParserConfigService} from '../../service/sensor-parser-config.service';
+import {StormService} from '../../service/storm.service';
+import {TopologyStatus} from '../../model/topology-status';
+import {SensorParserConfigHistoryService} from '../../service/sensor-parser-config-history.service';
+import {SensorParserConfigHistory} from '../../model/sensor-parser-config-history';
+import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service';
+import {SensorEnrichmentConfig} from '../../model/sensor-enrichment-config';
+import {RiskLevelRule} from '../../model/risk-level-rule';
+import {HdfsService} from '../../service/hdfs.service';
+import {RestError} from '../../model/rest-error';
+import {GrokValidationService} from '../../service/grok-validation.service';
+
+@Component({
+ selector: 'metron-config-sensor-parser-readonly',
+ templateUrl: 'sensor-parser-config-readonly.component.html',
+ styleUrls: ['sensor-parser-config-readonly.component.scss']
+})
+export class SensorParserConfigReadonlyComponent implements OnInit {
+
+ selectedSensorName: string;
+ startStopInProgress: boolean = false;
+ kafkaTopic: KafkaTopic = new KafkaTopic();
+ sensorParserConfigHistory: SensorParserConfigHistory = new SensorParserConfigHistory();
+ topologyStatus: TopologyStatus = new TopologyStatus();
+ sensorEnrichmentConfig: SensorEnrichmentConfig = new SensorEnrichmentConfig();
+ grokStatement: string = '';
+ transformsConfigKeys: string[] = [];
+ transformsConfigMap: {} = {};
+ rules: RiskLevelRule[] = [];
+ transformLinkText = 'show more';
+ threatTriageLinkText = 'show more';
+
+ editViewMetaData: {label?: string, value?: string, type?: string, model?: string, boldTitle?: boolean}[] = [
+ {type: 'SEPARATOR', model: '', value: ''},
+ {label: 'PARSER', model: 'sensorParserConfigHistory', value: 'parserClassName'},
+ {label: 'LAST UPDATED', model: 'sensorParserConfigHistory', value: 'modifiedByDate'},
+ {label: 'LAST EDITOR', model: 'sensorParserConfigHistory', value: 'modifiedBy'},
+ {label: 'STATE', model: 'topologyStatus', value: 'sensorStatus'},
+ {label: 'ORIGINATOR', model: 'sensorParserConfigHistory', value: 'createdBy'},
+ {label: 'CREATION DATE', model: 'sensorParserConfigHistory', value: 'createdDate'},
+
+ {type: 'SPACER', model: '', value: ''},
+
+ {label: 'STORM', model: 'topologyStatus', value: 'status', boldTitle: true},
+ {label: 'LATENCY', model: 'topologyStatus', value: 'latency'},
+ {label: 'THROUGHPUT', model: 'topologyStatus', value: 'throughput'},
+ {label: 'EMITTED(10 MIN)', model: 'topologyStatus', value: 'emitted'},
+ {label: 'ACKED(10 MIN)', model: 'topologyStatus', value: 'acked'},
+
+ {type: 'SPACER', model: '', value: ''},
+
+ {label: 'KAFKA', model: 'kafkaTopic', value: 'currentKafkaStatus', boldTitle: true},
+ {label: 'PARTITONS', model: 'kafkaTopic', value: 'numPartitions'},
+ {label: 'REPLICATION FACTOR', model: 'kafkaTopic', value: 'replicationFactor'},
+ {type: 'SEPARATOR', model: '', value: ''},
+
+ {label: '', model: 'grokStatement', value: 'grokPattern'},
+
+ {type: 'TITLE', model: '', value: 'Schema'},
+ {label: '', model: 'transforms', value: ''},
+ {type: 'SEPARATOR', model: '', value: ''},
+
+ {type: 'TITLE', model: '', value: 'Threat Triage Rules'},
+ {label: '', model: 'threatTriageRules', value: ''}
+
+ ];
+
+ constructor(private sensorParserConfigHistoryService: SensorParserConfigHistoryService,
+ private sensorParserConfigService: SensorParserConfigService,
+ private sensorEnrichmentService: SensorEnrichmentConfigService,
+ private stormService: StormService,
+ private kafkaService: KafkaService,
+ private hdfsService: HdfsService,
+ private grokValidationService: GrokValidationService,
+ private activatedRoute: ActivatedRoute, private router: Router,
+ private metronAlerts: MetronAlerts) {
+ }
+
+ getSensorInfo(): void {
+ this.sensorParserConfigHistoryService.get(this.selectedSensorName).subscribe(
+ (results: SensorParserConfigHistory) => {
+ this.sensorParserConfigHistory = results;
+ this.setGrokStatement();
+ this.setTransformsConfigKeys();
+
+ let items = this.sensorParserConfigHistory.config.parserClassName.split('.');
+ this.sensorParserConfigHistory['parserClassName'] = items[items.length - 1].replace('Basic', '').replace('Parser', '');
+
+ });
+ }
+
+ getSensorStatusService() {
+ this.stormService.getStatus(this.selectedSensorName).subscribe(
+ (results: TopologyStatus) => {
+ this.topologyStatus = results;
+ },
+ error => {
+ this.topologyStatus.status = 'Stopped';
+ });
+ }
+
+ getKafkaData(): void {
+ this.kafkaService.get(this.selectedSensorName).subscribe(
+ (results: KafkaTopic) => {
+ this.kafkaTopic = results;
+ this.kafkaService.sample(this.selectedSensorName).subscribe((sampleData: string) => {
+ this.kafkaTopic['currentKafkaStatus'] = (sampleData && sampleData.length > 0) ? 'Emitting' : 'Not Emitting';
+ },
+ error => {
+ this.kafkaTopic['currentKafkaStatus'] = 'Not Emitting';
+ });
+ }, error => {
+ this.kafkaTopic['currentKafkaStatus'] = 'No Kafka Topic';
+ });
+ }
+
+ getEnrichmentData() {
+ this.sensorEnrichmentService.get(this.selectedSensorName).subscribe((sensorEnrichmentConfig) => {
+ this.sensorEnrichmentConfig = sensorEnrichmentConfig;
+ this.rules = sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules;
+ });
+ }
+
+ getTopologyStatus(key: string): string {
+ if (key === 'latency') {
+ return this.topologyStatus.latency >= 0? (this.topologyStatus.latency + 's') : '-';
+ } else if (key === 'throughput') {
+ return this.topologyStatus.throughput >= 0 ? ((Math.round(this.topologyStatus.throughput * 100) / 100) + 'kb/s') : '-';
+ } else if (key === 'emitted') {
+ return this.topologyStatus.emitted >= 0 ? (this.topologyStatus.emitted + '') : '-';
+ } else if (key === 'acked') {
+ return this.topologyStatus.acked >= 0 ? (this.topologyStatus.acked + '') : '-';
+ } else if (key === 'sensorStatus') {
+ if (this.topologyStatus.status === 'ACTIVE') {
+ return 'Enabled';
+ } else if (this.topologyStatus.status === 'INACTIVE') {
+ return 'Disabled';
+ } else {
+ return '-';
+ }
+ } else if (key === 'status') {
+ if (this.topologyStatus.status === 'ACTIVE') {
+ return 'Running';
+ } else if (this.topologyStatus.status === 'INACTIVE') {
+ return 'Disabled';
+ } else {
+ return 'Stopped';
+ }
+ }
+
+ return this.topologyStatus[key] ? this.topologyStatus[key] : '-';
+ }
+
+ ngOnInit() {
+ this.activatedRoute.params.subscribe(params => {
+ this.selectedSensorName = params['id'];
+ this.getData();
+ });
+ }
+
+ getData() {
+ this.startStopInProgress = false;
+
+ this.getSensorInfo();
+ this.getSensorStatusService();
+ this.getKafkaData();
+ this.getEnrichmentData();
+ }
+
+ setGrokStatement() {
+ if (this.sensorParserConfigHistory.config && this.sensorParserConfigHistory.config.parserConfig) {
+ let path = this.sensorParserConfigHistory.config.parserConfig['grokPath'];
+ if (path) {
+ this.hdfsService.read(path).subscribe(contents => {
+ this.grokStatement = contents;
+ }, (hdfsError: RestError) => {
+ this.grokValidationService.getStatement(path).subscribe(contents => {
+ this.grokStatement = contents;
+ }, (grokError: RestError) => {
+ this.metronAlerts.showErrorMessage('Could not find grok statement in HDFS or classpath at ' + path);
+ });
+ });
+ }
+ }
+ }
+
+ setTransformsConfigKeys() {
+ if (this.sensorParserConfigHistory.config && this.sensorParserConfigHistory.config.fieldTransformations &&
+ this.sensorParserConfigHistory.config.fieldTransformations.length > 0) {
+ this.transformsConfigKeys = [];
+ for (let transforms of this.sensorParserConfigHistory.config.fieldTransformations) {
+ if (transforms.config) {
+ for (let key of Object.keys(transforms.config)) {
+ if (this.transformsConfigKeys.indexOf(key) === -1) {
+ this.transformsConfigMap[key] = [];
+ this.transformsConfigKeys.push(key);
+ }
+ this.transformsConfigMap[key].push(transforms.config[key]);
+ }
+ }
+ }
+ this.transformsConfigKeys = this.transformsConfigKeys.sort();
+ }
+ }
+
+ getTransformsOutput(): string {
+ if (this.sensorParserConfigHistory.config && this.sensorParserConfigHistory.config.fieldTransformations &&
+ this.sensorParserConfigHistory.config.fieldTransformations.length > 0) {
+ let output = [];
+ for (let transforms of this.sensorParserConfigHistory.config.fieldTransformations) {
+ if (transforms.output) {
+ output = output.concat(transforms.output);
+ }
+ }
+ output = output.sort().filter(function(item, pos, self) {
+ return self.indexOf(item) === pos;
+ });
+
+ return output.join(', ');
+ }
+
+ return '-';
+ }
+
+ goBack() {
+ this.router.navigateByUrl('/sensors');
+ }
+
+ onEditSensor() {
+ this.router.navigateByUrl('/sensors(dialog:sensors-config/' + this.selectedSensorName + ')');
+ }
+
+ onStartSensor() {
+ this.toggleStartStopInProgress();
+ let name = this.selectedSensorName;
+
+ this.stormService.startParser(name).subscribe(result => {
+ this.metronAlerts.showSuccessMessage('Started sensor ' + name);
+ this.toggleStartStopInProgress();
+ this.getData();
+ },
+ error => {
+ this.metronAlerts.showErrorMessage('Unable to start sensor ' + name);
+ this.toggleStartStopInProgress();
+ });
+ }
+
+ onStopSensor() {
+ this.toggleStartStopInProgress();
+
+ let name = this.selectedSensorName;
+ this.stormService.stopParser(name).subscribe(result => {
+ this.metronAlerts.showSuccessMessage('Stopped sensor ' + name);
+ this.toggleStartStopInProgress();
+ this.getData();
+ },
+ error => {
+ this.metronAlerts.showErrorMessage('Unable to stop sensor ' + name);
+ this.toggleStartStopInProgress();
+ });
+ }
+
+ onEnableSensor() {
+ this.toggleStartStopInProgress();
+
+ let name = this.selectedSensorName;
+ this.stormService.activateParser(name).subscribe(result => {
+ this.metronAlerts.showSuccessMessage('Enabled sensor ' + name);
+ this.toggleStartStopInProgress();
+ this.getData();
+ },
+ error => {
+ this.metronAlerts.showErrorMessage('Unable to enabled sensor ' + name);
+ this.toggleStartStopInProgress();
+ });
+ }
+
+ onDisableSensor() {
+ this.toggleStartStopInProgress();
+
+ let name = this.selectedSensorName;
+ this.stormService.deactivateParser(name).subscribe(result => {
+ this.metronAlerts.showSuccessMessage('Disabled sensor ' + name);
+ this.toggleStartStopInProgress();
+ this.getData();
+ },
+ error => {
+ this.metronAlerts.showErrorMessage('Unable to disable sensor ' + name);
+ this.toggleStartStopInProgress();
+ });
+ }
+
+ onDeleteSensor() {
+ this.toggleStartStopInProgress();
+
+ let name = this.selectedSensorName;
+ this.sensorParserConfigService.deleteSensorParserConfig(name).subscribe(result => {
+ this.metronAlerts.showSuccessMessage('Deleted sensor ' + name);
+ this.toggleStartStopInProgress();
+ this.sensorParserConfigService.dataChangedSource.next([this.sensorParserConfigHistory.config]);
+ this.goBack();
+ },
+ error => {
+ this.metronAlerts.showErrorMessage('Unable to delete sensor ' + name);
+ this.toggleStartStopInProgress();
+ });
+ }
+
+ toggleStartStopInProgress() {
+ this.startStopInProgress = !this.startStopInProgress;
+ }
+
+ getRuleDisplayName(): string {
+ return this.rules.map(x => this.getDisplayName(x)).join(', ');
+ }
+
+ getDisplayName(riskLevelRule: RiskLevelRule): string {
+ if (riskLevelRule.name) {
+ return riskLevelRule.name;
+ } else {
+ return riskLevelRule.rule ? riskLevelRule.rule : '';
+ }
+ }
+
+ toggleTransformLink() {
+ return this.transformLinkText = (this.transformLinkText === 'show more') ? 'show less' : 'show more';
+ }
+
+ toggleThreatTriageLink() {
+ return this.threatTriageLinkText = (this.threatTriageLinkText === 'show more') ? 'show less' : 'show more';
+ }
+
+ isStartHidden() {
+ return (this.topologyStatus.status === 'ACTIVE' || this.topologyStatus.status === 'INACTIVE');
+ }
+
+ isStopHidden() {
+ return ((this.topologyStatus.status === 'KILLED' || this.topologyStatus.status === 'Stopped'));
+ }
+
+ isEnableHidden() {
+ return (this.topologyStatus.status === 'ACTIVE' || this.topologyStatus.status === 'KILLED'
+ || this.topologyStatus.status === 'Stopped');
+ }
+
+ isDisableHidden() {
+ return (this.topologyStatus.status === 'INACTIVE' || this.topologyStatus.status === 'KILLED'
+ || this.topologyStatus.status === 'Stopped');
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.module.ts
new file mode 100644
index 0000000..c20d377
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.module.ts
@@ -0,0 +1,27 @@
+/**
+ * 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-config-readonly.routing';
+import {SharedModule} from '../../shared/shared.module';
+import {SensorParserConfigReadonlyComponent} from './sensor-parser-config-readonly.component';
+
+@NgModule ({
+ imports: [ routing, SharedModule ],
+ declarations: [ SensorParserConfigReadonlyComponent ]
+})
+export class SensorParserConfigReadonlyModule { }
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.routing.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.routing.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.routing.ts
new file mode 100644
index 0000000..71e1bad
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.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 {SensorParserConfigReadonlyComponent} from './sensor-parser-config-readonly.component';
+import {AuthGuard} from '../../shared/auth-guard';
+
+export const routing: ModuleWithProviders = RouterModule.forChild([
+ { path: 'sensors-readonly/:id', component: SensorParserConfigReadonlyComponent, canActivate: [AuthGuard], outlet: 'dialog'}
+]);
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config/index.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config/index.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/index.ts
new file mode 100644
index 0000000..f108057
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/index.ts
@@ -0,0 +1 @@
+export * from './sensor-parser-config.component';
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.html b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.html
new file mode 100644
index 0000000..33e8fd5
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.html
@@ -0,0 +1,181 @@
+<!--
+ 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.
+ -->
+<metron-config-metron-modal>
+ <metron-config-sensor-grok [hidden]="!showGrokValidator" [showGrok]="showGrokValidator"
+ [grokStatement]="grokStatement"
+ [(patternLabel)]="patternLabel"
+ [(sensorParserConfig)]="sensorParserConfig"
+ (hideGrok)="hidePane(pane.GROK)" (onSaveGrokStatement)="onSaveGrokStatement($event)" (onSavePatternLabel)="onSavePatternLabel($event)"></metron-config-sensor-grok>
+
+ <metron-config-sensor-raw-json [hidden]="!showRawJson" [showRawJson]="showRawJson" (hideRawJson)="hidePane(pane.RAWJSON)"
+ [(sensorParserConfig)]="sensorParserConfig"
+ [(sensorEnrichmentConfig)]="sensorEnrichmentConfig"
+ [(indexingConfigurations)]="indexingConfigurations"
+ (onRawJsonChanged)="onRawJsonChanged()"></metron-config-sensor-raw-json>
+
+ <metron-config-sensor-field-schema [hidden]="!showFieldSchema" [showFieldSchema]="showFieldSchema"
+ [grokStatement]="grokStatement"
+ [(sensorParserConfig)]="sensorParserConfig"
+ [(sensorEnrichmentConfig)]="sensorEnrichmentConfig"
+ (hideFieldSchema)="hidePane(pane.FIELDSCHEMA)"></metron-config-sensor-field-schema>
+
+ <metron-config-sensor-threat-triage [hidden]="!showThreatTriage" [showThreatTriage]="showThreatTriage"
+ [(sensorEnrichmentConfig)]="sensorEnrichmentConfig"
+ (hideThreatTriage)="hidePane(pane.THREATTRIAGE)"></metron-config-sensor-threat-triage>
+
+
+ <div class="metron-slider-pane-edit fill load-right-to-left dialog1x" style="overflow: auto" >
+ <div style="height:100%">
+ <div class="form-title">{{sensorParserConfig.sensorTopic}} </div>
+ <i class="fa fa-times pull-right main close-button" aria-hidden="true" (click)="goBack()"></i>
+
+ <form role="form" [formGroup]="sensorConfigForm">
+ <div class="form-group">
+ <label attr.for="sensorTopic">NAME * </label>
+ <input type="text" class="form-control" name="sensorTopic" formControlName="sensorTopic" [(ngModel)]="sensorParserConfig.sensorTopic" (ngModelChange)="onSetSensorName()" [readonly]="editMode">
+ <label *ngIf="currentKafkaStatus !== null && currentKafkaStatus === kafkaStatus.NO_TOPIC"><span class="warning-text"> No Matching Kafka Topic </span></label>
+ <label *ngIf="currentKafkaStatus !== null && currentKafkaStatus === kafkaStatus.EMITTING" ><span class="success-text-color"> Kafka Topic Exists. Emitting </span></label>
+ <label *ngIf="currentKafkaStatus !== null && currentKafkaStatus === kafkaStatus.NOT_EMITTING"><span class="success-text-color"> Kafka Topic Exists. </span><span class="warning-text" > Not Emitting </span></label>
+ </div>
+
+ <div class="form-group">
+ <label attr.for="parserClassName">PARSER TYPE * </label>
+ <select class="form-control" formControlName="parserClassName" [(ngModel)]="sensorParserConfig.parserClassName" (ngModelChange)="onParserTypeChange()" >
+ <option *ngFor="let parserName of availableParserNames" [value]="availableParsers[parserName]">{{parserName}}</option>
+ </select>
+ </div>
+
+ <div class="form-group" [ngClass]="{'panel-selected': showGrokValidator }" *ngIf="isGrokParser(sensorParserConfig)" >
+ <label attr.for="grokStatement">GROK STATEMENT</label>
+ <div class="input-group" [attr.disabled]="!sensorNameValid || !parserClassValid">
+ <input type="text" class="form-control" formControlName="grokStatement" [(ngModel)]="this.grokStatement" (ngModelChange)="onGrokStatementChange()" readonly>
+ <span class="input-group-btn">
+ <button class="btn btn-default" type="button" (click)="sensorNameValid && parserClassValid && onShowGrokPane()" readonly>
+ <i class="fa fa-columns" aria-hidden="true"></i>
+ <i class="fa fa-angle-double-right" style="padding-left: 3px" aria-hidden="true"></i>
+ </button>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group" [ngClass]="{'panel-selected': showFieldSchema }" >
+ <label attr.for="fieldSchema">SCHEMA</label>
+ <div class="input-group" [attr.disabled]="!configValid">
+ <div class="form-control" style="height: 80px; resize: none;" readonly>
+ <table cellspacing="10">
+ <tr> <td class="p-l-1">TRANSFORMATIONS </td> <td class="p-l-1"> </td><td class="p-1-1">{{getTransformationCount()}}</td> </tr>
+ <tr> <td>ENRICHMENTS</td> <td class="p-l-1"> </td> <td class="p-1-1">{{getEnrichmentCount()}}</td></tr>
+ <tr> <td>THREAT INTEL</td> <td class="p-l-1"> </td> <td class="p-1-1">{{getThreatIntelCount()}}</td></tr>
+ </table>
+ </div>
+ <span class="input-group-btn">
+ <button class="btn btn-default" type="button" (click)="configValid && showPane(pane.FIELDSCHEMA)" style="height: 80px;" readonly>
+ <i class="fa fa-columns" aria-hidden="true"></i>
+ <i class="fa fa-angle-double-right" style="padding-left: 3px" aria-hidden="true"></i>
+ </button>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group" [ngClass]="{'panel-selected': showThreatTriage }">
+ <label attr.for="stellar">THREAT TRIAGE</label>
+ <div class="input-group" [attr.disabled]="!configValid">
+ <div class="form-control" style="resize: none;" readonly>
+ <table style="margin: 0">
+ <tr> <td class="p-l-1">RULES </td> <td class="p-l-1"> </td><td class="p-1-1">{{getRuleCount()}}</td> </tr>
+ </table>
+ </div>
+ <span class="input-group-btn">
+ <button class="btn btn-default" type="button" (click)="configValid && showPane(pane.THREATTRIAGE)" readonly>
+ <i class="fa fa-columns" aria-hidden="true"></i>
+ <i class="fa fa-angle-double-right" style="padding-left: 3px" aria-hidden="true"></i>
+ </button>
+ </span>
+ </div>
+ </div>
+
+ <div [hidden]="!showAdvancedParserConfiguration">
+ <div class="form-group">
+ <div class="form-seperator-edit"></div>
+ <div class="advanced-title">Advanced</div>
+ <i class="fa fa-times pull-right small-close-button" aria-hidden="true" (click)="onAdvancedConfigFormClose()"></i>
+ </div>
+ <div class="form-group" [ngClass]="{'panel-selected': showRawJson }">
+ <label attr.for="stellar">RAW JSON</label>
+ <div class="input-group">
+ <div class="form-control" style="resize: none;" readonly>Select</div>
+ <span class="input-group-btn">
+ <button class="btn btn-default" type="button" (click)="showPane(pane.RAWJSON)" readonly>
+ <i class="fa fa-columns" aria-hidden="true"></i>
+ <i class="fa fa-angle-double-right" style="padding-left: 3px" aria-hidden="true"></i>
+ </button>
+ </span>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label attr.for="index">HDFS INDEX NAME</label>
+ <input type="text" class="form-control" name="hdfsIndex" formControlName="hdfsIndex" [(ngModel)]="indexingConfigurations.hdfs.index" >
+ </div>
+ <div class="form-group">
+ <label attr.for="batchSize">HDFS BATCH SIZE</label>
+ <metron-config-number-spinner name="hdfsBatchSize" [(ngModel)]="indexingConfigurations.hdfs.batchSize" formControlName="hdfsBatchSize"> </metron-config-number-spinner>
+ </div>
+ <div class="form-group">
+ <label attr.for="index">HDFS ENABLED</label>
+ <input type="checkbox" class="form-control" name="hdfsEnabled" formControlName="hdfsEnabled" [(ngModel)]="indexingConfigurations.hdfs.enabled" >
+ </div>
+ <div class="form-group">
+ <label attr.for="index">ELASTICSEARCH INDEX NAME</label>
+ <input type="text" class="form-control" name="elasticsearchIndex" formControlName="elasticsearchIndex" [(ngModel)]="indexingConfigurations.elasticsearch.index" >
+ </div>
+ <div class="form-group">
+ <label attr.for="batchSize">ELASTICSEARCH BATCH SIZE</label>
+ <metron-config-number-spinner name="elasticsearchBatchSize" [(ngModel)]="indexingConfigurations.elasticsearch.batchSize" formControlName="elasticsearchBatchSize"> </metron-config-number-spinner>
+ </div>
+ <div class="form-group">
+ <label attr.for="index">ELASTICSEARCH ENABLED</label>
+ <input type="checkbox" class="form-control" name="elasticsearchEnabled" formControlName="elasticsearchEnabled" [(ngModel)]="indexingConfigurations.elasticsearch.enabled" >
+ </div>
+ <div class="form-group">
+ <label attr.for="index">SOLR INDEX NAME</label>
+ <input type="text" class="form-control" name="solrIndex" formControlName="solrIndex" [(ngModel)]="indexingConfigurations.solr.index" >
+ </div>
+ <div class="form-group">
+ <label attr.for="batchSize">SOLR BATCH SIZE</label>
+ <metron-config-number-spinner name="solrBatchSize" [(ngModel)]="indexingConfigurations.solr.batchSize" formControlName="solrBatchSize"> </metron-config-number-spinner>
+ </div>
+ <div class="form-group">
+ <label attr.for="index">SOLR ENABLED</label>
+ <input type="checkbox" class="form-control" name="solrEnabled" formControlName="solrEnabled" [(ngModel)]="indexingConfigurations.solr.enabled" >
+ </div>
+ <div class="form-group">
+ <label attr.for="parserConfig">PARSER CONFIG</label>
+ <metron-config-advanced-form name="parserConfig" [(config)]="sensorParserConfig.parserConfig"></metron-config-advanced-form>
+ </div>
+
+ </div>
+
+ <div class="form-group">
+ <div class="form-seperator-edit"></div>
+ <div class="button-row">
+ <button type="submit" class="btn save-button" [ngClass]="{'disabled':!configValid}" (click)="onSave()">SAVE</button>
+ <button class="btn form-enable-disable-button" (click)="goBack()" >CANCEL</button>
+ <span class="advanced-link" [hidden]="showAdvancedParserConfiguration" (click)="showAdvancedParserConfiguration = true">Advanced</span>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+</metron-config-metron-modal>
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.scss
new file mode 100644
index 0000000..298163a
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.scss
@@ -0,0 +1,120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@import "../../_variables.scss";
+@import "../../../styles.scss";
+
+.form-title
+{
+ padding-left: 25px;
+}
+
+.form-group
+{
+ padding-left: 25px;
+ padding-right: 20px;
+ padding-bottom: 10px;
+}
+
+.close-button
+{
+ padding-right: 20px;
+}
+
+.advanced-link {
+ padding-left: 10px;
+ cursor: pointer;
+ color: $field-button-color;
+ font-size: 14px;
+
+}
+
+.advanced-title {
+ font-size: 16px;
+ color: $form-field-text-color;
+ display: inline-block;
+}
+
+.small-close-button {
+ font-size: 16px;
+ padding-right: 10px;
+ cursor: pointer;
+}
+
+.input-placeholder {
+ font-size: 11px;
+ font-style: italic;
+ color:#999999;
+}
+
+.metron-slider-pane-edit {
+ background: $edit-background;
+}
+
+.input-group[disabled='true'] {
+ cursor: not-allowed;
+ opacity: .65;
+ background-color: #333333;
+
+ .btn {
+ cursor: inherit;
+ }
+
+ table tr td {
+ border: none !important;
+ }
+}
+
+.button-container {
+ padding-top: 5px;
+}
+
+.button-row {
+ padding-top: 10px;
+}
+
+.form-enable-disable-button {
+ width: 32%;
+}
+
+.save-button {
+ background-color: $form-button-border;
+ border-color: $form-button-border;
+ color: white;
+ font-size: 14px;
+ width: 32%;
+
+ &:hover
+ {
+
+ }
+
+ &:focus
+ {
+ outline: none;
+ }
+}
+
+.panel-selected {
+ background-color: $edit-background-border;
+}
+
+metron-config-sensor-grok, metron-config-sensor-raw-json, metron-config-sensor-field-schema,
+metron-config-sensor-threat-triage, metron-config-sensor-threat-triage
+{
+ @extend .flexbox-row-reverse;
+}