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">&nbsp;&nbsp;</td><td class="p-1-1">{{getTransformationCount()}}</td> </tr>
+                                <tr> <td>ENRICHMENTS</td> <td class="p-l-1">&nbsp;&nbsp;</td> <td class="p-1-1">{{getEnrichmentCount()}}</td></tr>
+                                <tr> <td>THREAT INTEL</td> <td class="p-l-1">&nbsp;&nbsp;</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">&nbsp;&nbsp;</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;
+}