http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts
 
b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts
new file mode 100644
index 0000000..958b27d
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.ts
@@ -0,0 +1,58 @@
+/**
+ * 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 {Injectable, Inject} from '@angular/core';
+import {Http, Headers, RequestOptions, Response} from '@angular/http';
+import {Observable} from 'rxjs/Observable';
+import {IndexingConfigurations} from '../model/sensor-indexing-config';
+import {HttpUtil} from '../util/httpUtil';
+import {IAppConfig} from '../app.config.interface';
+import {APP_CONFIG} from '../app.config';
+
+@Injectable()
+export class SensorIndexingConfigService {
+  url = this.config.apiEndpoint + '/sensor/indexing/config';
+  defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 
'XMLHttpRequest'};
+
+  constructor(private http: Http, @Inject(APP_CONFIG) private config: 
IAppConfig) {
+  }
+
+  public post(name: string, sensorIndexingConfig: IndexingConfigurations): 
Observable<IndexingConfigurations> {
+    return this.http.post(this.url + '/' + name, 
JSON.stringify(sensorIndexingConfig),
+                          new RequestOptions({headers: new 
Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public get(name: string): Observable<IndexingConfigurations> {
+    return this.http.get(this.url + '/' + name, new RequestOptions({headers: 
new Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public getAll(): Observable<IndexingConfigurations[]> {
+    return this.http.get(this.url, new RequestOptions({headers: new 
Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public deleteSensorIndexingConfig(name: string): Observable<Response> {
+    return this.http.delete(this.url + '/' + name, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+      .catch(HttpUtil.handleError);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts
 
b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts
new file mode 100644
index 0000000..3da4065
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.spec.ts
@@ -0,0 +1,97 @@
+/**
+ * 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, inject, TestBed} from '@angular/core/testing';
+import {MockBackend, MockConnection} from '@angular/http/testing';
+import {SensorParserConfig} from '../model/sensor-parser-config';
+import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from 
'@angular/http';
+import '../rxjs-operators';
+import {METRON_REST_CONFIG, APP_CONFIG} from '../app.config';
+import {SensorParserConfigHistoryService} from 
'./sensor-parser-config-history.service';
+import {IAppConfig} from '../app.config.interface';
+import {SensorParserConfigHistory} from 
'../model/sensor-parser-config-history';
+
+describe('SensorParserConfigHistoryService', () => {
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [HttpModule],
+      providers: [
+        SensorParserConfigHistoryService,
+        {provide: XHRBackend, useClass: MockBackend},
+        {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+      ]
+    })
+        .compileComponents();
+  }));
+
+  it('can instantiate service when inject service',
+      inject([SensorParserConfigHistoryService], (service: 
SensorParserConfigHistoryService) => {
+        expect(service instanceof SensorParserConfigHistoryService).toBe(true);
+      }));
+
+  it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: 
Http, config: IAppConfig) => {
+    expect(http).not.toBeNull('http should be provided');
+    let service = new SensorParserConfigHistoryService(http, config);
+    expect(service instanceof SensorParserConfigHistoryService).toBe(true, 
'new service should be ok');
+  }));
+
+
+  it('can provide the mockBackend as XHRBackend',
+      inject([XHRBackend], (backend: MockBackend) => {
+        expect(backend).not.toBeNull('backend should be provided');
+      }));
+
+  describe('when service functions', () => {
+    let sensorParserConfigHistoryService: SensorParserConfigHistoryService;
+    let mockBackend: MockBackend;
+    let sensorParserConfigHistory = new SensorParserConfigHistory();
+    let sensorParserConfig = new SensorParserConfig();
+    sensorParserConfig.sensorTopic = 'bro';
+    sensorParserConfigHistory.config = sensorParserConfig;
+    let sensorParserConfigHistoryResponse: Response;
+    let allSensorParserConfigHistoryResponse: Response;
+
+    beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: 
MockBackend, config: IAppConfig) => {
+      mockBackend = be;
+      sensorParserConfigHistoryService = new 
SensorParserConfigHistoryService(http, config);
+      sensorParserConfigHistoryResponse = new Response(new 
ResponseOptions({status: 200, body: sensorParserConfig}));
+      allSensorParserConfigHistoryResponse = new Response(new 
ResponseOptions({status: 200, body: [sensorParserConfig]}));
+    }));
+
+    it('get', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(sensorParserConfigHistoryResponse));
+
+      sensorParserConfigHistoryService.get('bro').subscribe(
+          result => {
+            expect(result).toEqual(sensorParserConfigHistory);
+          }, error => console.log(error));
+    })));
+
+    it('getAll', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(allSensorParserConfigHistoryResponse));
+
+      sensorParserConfigHistoryService.getAll().subscribe(
+          result => {
+            expect(result).toEqual([sensorParserConfigHistory]);
+          }, error => console.log(error));
+    })));
+  });
+
+});
+
+

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts
 
b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts
new file mode 100644
index 0000000..bd5cbc5
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/service/sensor-parser-config-history.service.ts
@@ -0,0 +1,61 @@
+/**
+ * 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 {Injectable, Inject} from '@angular/core';
+import {Http, Headers, RequestOptions, Response} from '@angular/http';
+import {Observable} from 'rxjs/Observable';
+import {HttpUtil} from '../util/httpUtil';
+import {IAppConfig} from '../app.config.interface';
+import {SensorParserConfigHistory} from 
'../model/sensor-parser-config-history';
+import {APP_CONFIG} from '../app.config';
+import {SensorParserConfig} from '../model/sensor-parser-config';
+
+@Injectable()
+export class SensorParserConfigHistoryService {
+  url =  this.config.apiEndpoint + '/sensor/parser/config';
+  defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 
'XMLHttpRequest'};
+
+  constructor(private http: Http, @Inject(APP_CONFIG) private config: 
IAppConfig) {
+
+  }
+
+  public get(name: string): Observable<SensorParserConfigHistory> {
+    return this.http.get(this.url + '/' + name, new RequestOptions({headers: 
new Headers(this.defaultHeaders)}))
+      .map((response: Response) => {
+        let sensorParserConfigHistory = new SensorParserConfigHistory();
+        sensorParserConfigHistory.config = response.json();
+        return sensorParserConfigHistory;
+      })
+      .catch(HttpUtil.handleError);
+  }
+
+  public getAll(): Observable<SensorParserConfigHistory[]> {
+    return this.http.get(this.url, new RequestOptions({headers: new 
Headers(this.defaultHeaders)}))
+      .map((response: Response) => {
+        let sensorParserConfigHistoryArray = [];
+        let sensorParserConfigs: SensorParserConfig[] = response.json();
+        for (let sensorParserConfig of sensorParserConfigs) {
+          let sensorParserConfigHistory = new SensorParserConfigHistory();
+          sensorParserConfigHistory.config = sensorParserConfig;
+          sensorParserConfigHistoryArray.push(sensorParserConfigHistory);
+        }
+        return sensorParserConfigHistoryArray;
+      })
+      .catch(HttpUtil.handleError);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts
 
b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts
new file mode 100644
index 0000000..bae4656
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.spec.ts
@@ -0,0 +1,149 @@
+/**
+ * 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, inject, TestBed} from '@angular/core/testing';
+import {MockBackend, MockConnection} from '@angular/http/testing';
+import {SensorParserConfigService} from './sensor-parser-config.service';
+import {SensorParserConfig} from '../model/sensor-parser-config';
+import {ParseMessageRequest} from '../model/parse-message-request';
+import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from 
'@angular/http';
+import '../rxjs-operators';
+import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config';
+import {IAppConfig} from '../app.config.interface';
+
+describe('SensorParserConfigService', () => {
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [HttpModule],
+      providers: [
+        SensorParserConfigService,
+        {provide: XHRBackend, useClass: MockBackend},
+        {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+      ]
+    })
+      .compileComponents();
+  }));
+
+  it('can instantiate service when inject service',
+    inject([SensorParserConfigService], (service: SensorParserConfigService) 
=> {
+      expect(service instanceof SensorParserConfigService).toBe(true);
+    }));
+
+  it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: 
Http, config: IAppConfig) => {
+    expect(http).not.toBeNull('http should be provided');
+    let service = new SensorParserConfigService(http, config);
+    expect(service instanceof SensorParserConfigService).toBe(true, 'new 
service should be ok');
+  }));
+
+
+  it('can provide the mockBackend as XHRBackend',
+    inject([XHRBackend], (backend: MockBackend) => {
+      expect(backend).not.toBeNull('backend should be provided');
+    }));
+
+  describe('when service functions', () => {
+    let sensorParserConfigService: SensorParserConfigService;
+    let mockBackend: MockBackend;
+    let sensorParserConfig = new SensorParserConfig();
+    sensorParserConfig.sensorTopic = 'bro';
+    sensorParserConfig.parserClassName = 'parserClass';
+    sensorParserConfig.parserConfig = {field: 'value'};
+    let availableParsers = [{ 'Grok': 'org.apache.metron.parsers.GrokParser'}];
+    let parseMessageRequest = new ParseMessageRequest();
+    parseMessageRequest.sensorParserConfig = new SensorParserConfig();
+    parseMessageRequest.sensorParserConfig.sensorTopic = 'bro';
+    parseMessageRequest.sampleData = 'sampleData';
+    let parsedMessage = { 'field': 'value'};
+    let sensorParserConfig1 = new SensorParserConfig();
+    sensorParserConfig1.sensorTopic = 'bro1';
+    let sensorParserConfig2 = new SensorParserConfig();
+    sensorParserConfig2.sensorTopic = 'bro2';
+    let deleteResult = {success: ['bro1', 'bro2']};
+    let sensorParserConfigResponse: Response;
+    let sensorParserConfigsResponse: Response;
+    let availableParserResponse: Response;
+    let parseMessageResponse: Response;
+    let deleteResponse: Response;
+
+    beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: 
MockBackend, config: IAppConfig) => {
+      mockBackend = be;
+      sensorParserConfigService = new SensorParserConfigService(http, config);
+      sensorParserConfigResponse = new Response(new ResponseOptions({status: 
200, body: sensorParserConfig}));
+      sensorParserConfigsResponse = new Response(new ResponseOptions({status: 
200, body: [sensorParserConfig]}));
+      availableParserResponse = new Response(new ResponseOptions({status: 200, 
body: availableParsers}));
+      parseMessageResponse = new Response(new ResponseOptions({status: 200, 
body: parsedMessage}));
+      deleteResponse = new Response(new ResponseOptions({status: 200, body: 
deleteResult}));
+    }));
+
+    it('post', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(sensorParserConfigResponse));
+
+      sensorParserConfigService.post(sensorParserConfig).subscribe(
+      result => {
+        expect(result).toEqual(sensorParserConfig);
+      }, error => console.log(error));
+    })));
+
+    it('get', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(sensorParserConfigResponse));
+
+      sensorParserConfigService.get('bro').subscribe(
+        result => {
+          expect(result).toEqual(sensorParserConfig);
+        }, error => console.log(error));
+    })));
+
+    it('getAll', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(sensorParserConfigsResponse));
+
+      sensorParserConfigService.getAll().subscribe(
+        results => {
+          expect(results).toEqual([sensorParserConfig]);
+        }, error => console.log(error));
+    })));
+
+    it('getAvailableParsers', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(availableParserResponse));
+
+      sensorParserConfigService.getAvailableParsers().subscribe(
+        results => {
+          expect(results).toEqual(availableParsers);
+        }, error => console.log(error));
+    })));
+
+    it('parseMessage', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(parseMessageResponse));
+
+      sensorParserConfigService.parseMessage(parseMessageRequest).subscribe(
+        results => {
+          expect(results).toEqual(parsedMessage);
+        }, error => console.log(error));
+    })));
+
+    it('deleteSensorParserConfigs', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(deleteResponse));
+
+      
sensorParserConfigService.deleteSensorParserConfigs([sensorParserConfig1, 
sensorParserConfig2]).subscribe(result => {
+        expect(result.success.length).toEqual(2);
+      });
+    })));
+  });
+
+});
+
+

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts
 
b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts
new file mode 100644
index 0000000..25cd833
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/service/sensor-parser-config.service.ts
@@ -0,0 +1,116 @@
+/**
+ * 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 {Injectable, Inject}     from '@angular/core';
+import {Http, Headers, RequestOptions, Response} from '@angular/http';
+import {Observable}     from 'rxjs/Observable';
+import {SensorParserConfig} from '../model/sensor-parser-config';
+import {HttpUtil} from '../util/httpUtil';
+import {Subject}    from 'rxjs/Subject';
+import {ParseMessageRequest} from '../model/parse-message-request';
+import {IAppConfig} from '../app.config.interface';
+import {APP_CONFIG} from '../app.config';
+
+@Injectable()
+export class SensorParserConfigService {
+  url = this.config.apiEndpoint + '/sensor/parser/config';
+  defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 
'XMLHttpRequest'};
+  selectedSensorParserConfig: SensorParserConfig;
+
+  dataChangedSource = new Subject<SensorParserConfig[]>();
+  dataChanged$ = this.dataChangedSource.asObservable();
+
+  constructor(private http: Http, @Inject(APP_CONFIG) private config: 
IAppConfig) {
+
+  }
+
+  public post(sensorParserConfig: SensorParserConfig): 
Observable<SensorParserConfig> {
+    return this.http.post(this.url, JSON.stringify(sensorParserConfig), new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public get(name: string): Observable<SensorParserConfig> {
+    return this.http.get(this.url + '/' + name, new RequestOptions({headers: 
new Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public getAll(): Observable<SensorParserConfig[]> {
+    return this.http.get(this.url, new RequestOptions({headers: new 
Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public deleteSensorParserConfig(name: string): Observable<Response> {
+    return this.http.delete(this.url + '/' + name, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+      .catch(HttpUtil.handleError);
+  }
+
+  public getAvailableParsers(): Observable<{}> {
+    return this.http.get(this.url + '/list/available', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public parseMessage(parseMessageRequest: ParseMessageRequest): 
Observable<{}> {
+    return this.http.post(this.url + '/parseMessage', parseMessageRequest, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public deleteSensorParserConfigs(sensors: SensorParserConfig[]): 
Observable<{success: Array<string>, failure: Array<string>}> {
+    let result: {success: Array<string>, failure: Array<string>} = {success: 
[], failure: []};
+    let observable = Observable.create((observer => {
+
+      let completed = () => {
+        if (observer) {
+          observer.next(result);
+          observer.complete();
+        }
+
+        this.dataChangedSource.next(sensors);
+      };
+
+      for (let i = 0; i < sensors.length; i++) {
+        
this.deleteSensorParserConfig(sensors[i].sensorTopic).subscribe(results => {
+          result.success.push(sensors[i].sensorTopic);
+          if (result.success.length + result.failure.length === 
sensors.length) {
+            completed();
+          }
+        }, error => {
+          result.failure.push(sensors[i].sensorTopic);
+          if (result.success.length + result.failure.length === 
sensors.length) {
+            completed();
+          }
+        });
+      }
+
+    }));
+
+    return observable;
+  }
+
+  public setSeletedSensor(sensor: SensorParserConfig): void {
+    this.selectedSensorParserConfig = sensor;
+  }
+
+  public getSelectedSensor(): SensorParserConfig {
+    return this.selectedSensorParserConfig;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/stellar.service.spec.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/service/stellar.service.spec.ts 
b/metron-interface/metron-config/src/app/service/stellar.service.spec.ts
new file mode 100644
index 0000000..163eefb
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/stellar.service.spec.ts
@@ -0,0 +1,142 @@
+/**
+ * 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, inject, TestBed} from '@angular/core/testing';
+import {MockBackend, MockConnection} from '@angular/http/testing';
+import {StellarService} from './stellar.service';
+import {SensorParserContext} from '../model/sensor-parser-context';
+import {SensorParserConfig} from '../model/sensor-parser-config';
+import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from 
'@angular/http';
+import '../rxjs-operators';
+import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config';
+import {IAppConfig} from '../app.config.interface';
+
+describe('StellarService', () => {
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [HttpModule],
+      providers: [
+        StellarService,
+        {provide: XHRBackend, useClass: MockBackend},
+        {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+      ]
+    })
+      .compileComponents();
+  }));
+
+  it('can instantiate service when inject service',
+    inject([StellarService], (service: StellarService) => {
+      expect(service instanceof StellarService).toBe(true);
+    }));
+
+  it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: 
Http, config: IAppConfig) => {
+    expect(http).not.toBeNull('http should be provided');
+    let service = new StellarService(http, config);
+    expect(service instanceof StellarService).toBe(true, 'new service should 
be ok');
+  }));
+
+
+  it('can provide the mockBackend as XHRBackend',
+    inject([XHRBackend], (backend: MockBackend) => {
+      expect(backend).not.toBeNull('backend should be provided');
+    }));
+
+  describe('when service functions', () => {
+    let transformationValidationService: StellarService;
+    let mockBackend: MockBackend;
+    let transformationRules = ['rule1', 'rule2'];
+    let transformationRulesValidation = {rule1: true, rule2: false};
+    let transformationValidation = new SensorParserContext();
+    transformationValidation.sampleData = {'data': 'data'};
+    transformationValidation.sensorParserConfig = new SensorParserConfig();
+    transformationValidation.sensorParserConfig.sensorTopic = 'test';
+    let transformations = ['STELLAR', 'REMOVE'];
+    let transformFunctions = [{'function1': 'desc1'}, {'function2': 'desc2'}];
+    let simpleTransformFunctions = [{'simplefunction1': 'simpledesc1'}, 
{'simplefunction2': 'simpledesc2'}];
+    let transformationRulesValidationResponse: Response;
+    let transformationValidationResponse: Response;
+    let transformationListResponse: Response;
+    let transformationListFunctionsResponse: Response;
+    let transformationListSimpleFunctionsResponse: Response;
+
+    beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: 
MockBackend, config: IAppConfig) => {
+      mockBackend = be;
+      transformationValidationService = new StellarService(http, config);
+      transformationRulesValidationResponse = new Response(new 
ResponseOptions({
+        status: 200,
+        body: transformationRulesValidation
+      }));
+      transformationValidationResponse = new Response(new ResponseOptions({
+        status: 200,
+        body: transformationValidation
+      }));
+      transformationListResponse = new Response(new ResponseOptions({status: 
200, body: transformations}));
+      transformationListFunctionsResponse = new Response(new 
ResponseOptions({status: 200, body: transformFunctions}));
+      transformationListSimpleFunctionsResponse = new Response(new 
ResponseOptions({status: 200, body: simpleTransformFunctions}));
+    }));
+
+    it('validateRules', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(transformationRulesValidationResponse));
+
+      
transformationValidationService.validateRules(transformationRules).subscribe(
+        result => {
+          expect(result).toEqual(transformationRulesValidation);
+        }, error => console.log(error));
+    })));
+
+    it('validate', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(transformationValidationResponse));
+
+      
transformationValidationService.validate(transformationValidation).subscribe(
+        result => {
+          expect(result).toEqual(transformationValidation);
+        }, error => console.log(error));
+    })));
+
+    it('list', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(transformationListResponse));
+
+      transformationValidationService.list().subscribe(
+        result => {
+          expect(result).toEqual(transformations);
+        }, error => console.log(error));
+    })));
+
+    it('listFunctions', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(transformationListFunctionsResponse));
+
+      transformationValidationService.listFunctions().subscribe(
+        result => {
+          expect(result).toEqual(transformFunctions);
+        }, error => console.log(error));
+    })));
+
+    it('listSimpleFunctions', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(transformationListSimpleFunctionsResponse));
+
+      transformationValidationService.listSimpleFunctions().subscribe(
+          result => {
+            expect(result).toEqual(simpleTransformFunctions);
+          }, error => console.log(error));
+    })));
+  });
+
+});
+
+
+

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/stellar.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/stellar.service.ts 
b/metron-interface/metron-config/src/app/service/stellar.service.ts
new file mode 100644
index 0000000..be04906
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/stellar.service.ts
@@ -0,0 +1,68 @@
+/**
+ * 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 {Injectable, Inject} from '@angular/core';
+import {Http, Headers, RequestOptions} from '@angular/http';
+import {Observable} from 'rxjs/Observable';
+import {SensorParserContext} from '../model/sensor-parser-context';
+import {HttpUtil} from '../util/httpUtil';
+import {StellarFunctionDescription} from 
'../model/stellar-function-description';
+import {IAppConfig} from '../app.config.interface';
+import {APP_CONFIG} from '../app.config';
+
+@Injectable()
+export class StellarService {
+  url = this.config.apiEndpoint + '/stellar';
+  defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 
'XMLHttpRequest'};
+
+  constructor(private http: Http, @Inject(APP_CONFIG) private config: 
IAppConfig) {
+
+  }
+
+  public validateRules(rules: string[]): Observable<{}> {
+    return this.http.post(this.url + '/validate/rules', JSON.stringify(rules),
+      new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public validate(transformationValidation: SensorParserContext): 
Observable<{}> {
+    return this.http.post(this.url + '/validate', 
JSON.stringify(transformationValidation),
+      new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public list(): Observable<string[]> {
+    return this.http.get(this.url + '/list', new RequestOptions({headers: new 
Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public listFunctions(): Observable<StellarFunctionDescription[]> {
+    return this.http.get(this.url + '/list/functions', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+  public listSimpleFunctions(): Observable<StellarFunctionDescription[]> {
+    return this.http.get(this.url + '/list/simple/functions', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+      .map(HttpUtil.extractData)
+      .catch(HttpUtil.handleError);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/storm.service.spec.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/service/storm.service.spec.ts 
b/metron-interface/metron-config/src/app/service/storm.service.spec.ts
new file mode 100644
index 0000000..528c1c3
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/storm.service.spec.ts
@@ -0,0 +1,252 @@
+/**
+ * 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, inject, TestBed} from '@angular/core/testing';
+import {MockBackend, MockConnection} from '@angular/http/testing';
+import {TopologyStatus} from '../model/topology-status';
+import {TopologyResponse} from '../model/topology-response';
+import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from 
'@angular/http';
+import '../rxjs-operators';
+import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config';
+import {IAppConfig} from '../app.config.interface';
+import {StormService} from './storm.service';
+
+describe('StormService', () => {
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [HttpModule],
+      providers: [
+        StormService,
+        {provide: XHRBackend, useClass: MockBackend},
+        {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+      ]
+    })
+        .compileComponents();
+  }));
+
+  it('can instantiate service when inject service',
+      inject([StormService], (service: StormService) => {
+        expect(service instanceof StormService).toBe(true);
+      }));
+
+  it('can instantiate service with "new"', inject([Http, APP_CONFIG], (http: 
Http, config: IAppConfig) => {
+    expect(http).not.toBeNull('http should be provided');
+    let service = new StormService(http, config);
+    expect(service instanceof StormService).toBe(true, 'new service should be 
ok');
+  }));
+
+
+  it('can provide the mockBackend as XHRBackend',
+      inject([XHRBackend], (backend: MockBackend) => {
+        expect(backend).not.toBeNull('backend should be provided');
+      }));
+
+  describe('when service functions', () => {
+    let stormService: StormService;
+    let mockBackend: MockBackend;
+    let allStatuses: TopologyStatus[] = [];
+    let broStatus = new TopologyStatus();
+    broStatus.name = 'bro';
+    broStatus.id = 'broid';
+    broStatus.status = 'ACTIVE';
+    allStatuses.push(broStatus);
+    let enrichmentStatus = new TopologyStatus();
+    enrichmentStatus.name = 'enrichment';
+    enrichmentStatus.id = 'enrichmentid';
+    enrichmentStatus.status = 'ACTIVE';
+    allStatuses.push(enrichmentStatus);
+    let indexingStatus = new TopologyStatus();
+    indexingStatus.name = 'indexing';
+    indexingStatus.id = 'indexingid';
+    indexingStatus.status = 'ACTIVE';
+    allStatuses.push(indexingStatus);
+    let startMessage: TopologyResponse = {status: 'success', message: 
'STARTED'};
+    let stopMessage: TopologyResponse = {status: 'success', message: 
'STOPPED'};
+    let activateMessage: TopologyResponse = {status: 'success', message: 
'ACTIVE'};
+    let deactivateMessage: TopologyResponse = {status: 'success', message: 
'INACTIVE'};
+    let allStatusesResponse: Response;
+    let enrichmentStatusResponse: Response;
+    let indexingStatusResponse: Response;
+    let broStatusResponse: Response;
+    let startResponse: Response;
+    let stopResponse: Response;
+    let activateResponse: Response;
+    let deactivateResponse: Response;
+
+    beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: 
MockBackend, config: IAppConfig) => {
+      mockBackend = be;
+      stormService = new StormService(http, config);
+      allStatusesResponse = new Response(new ResponseOptions({status: 200, 
body: allStatuses}));
+      enrichmentStatusResponse = new Response(new ResponseOptions({status: 
200, body: enrichmentStatus}));
+      indexingStatusResponse = new Response(new ResponseOptions({status: 200, 
body: indexingStatus}));
+      broStatusResponse = new Response(new ResponseOptions({status: 200, body: 
broStatus}));
+      startResponse = new Response(new ResponseOptions({status: 200, body: 
startMessage}));
+      stopResponse = new Response(new ResponseOptions({status: 200, body: 
stopMessage}));
+      activateResponse = new Response(new ResponseOptions({status: 200, body: 
activateMessage}));
+      deactivateResponse = new Response(new ResponseOptions({status: 200, 
body: deactivateMessage}));
+    }));
+
+    it('getAll', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(allStatusesResponse));
+
+      stormService.getAll().subscribe(
+          result => {
+            expect(result).toEqual(allStatuses);
+          }, error => console.log(error));
+    })));
+
+    it('getEnrichmentStatus', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(enrichmentStatusResponse));
+
+      stormService.getEnrichmentStatus().subscribe(
+          result => {
+            expect(result).toEqual(enrichmentStatus);
+          }, error => console.log(error));
+    })));
+
+    it('activateEnrichment', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(activateResponse));
+
+      stormService.activateEnrichment().subscribe(
+          result => {
+            expect(result).toEqual(activateMessage);
+          }, error => console.log(error));
+    })));
+
+    it('deactivateEnrichment', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(deactivateResponse));
+
+      stormService.deactivateEnrichment().subscribe(
+          result => {
+            expect(result).toEqual(deactivateMessage);
+          }, error => console.log(error));
+    })));
+
+    it('startEnrichment', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(startResponse));
+
+      stormService.startEnrichment().subscribe(
+          result => {
+            expect(result).toEqual(startMessage);
+          }, error => console.log(error));
+    })));
+
+    it('stopEnrichment', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(stopResponse));
+
+      stormService.stopEnrichment().subscribe(
+          result => {
+            expect(result).toEqual(stopMessage);
+          }, error => console.log(error));
+    })));
+
+    it('getIndexingStatus', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(indexingStatusResponse));
+
+      stormService.getIndexingStatus().subscribe(
+          result => {
+            expect(result).toEqual(indexingStatus);
+          }, error => console.log(error));
+    })));
+
+    it('activateIndexing', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(activateResponse));
+
+      stormService.activateIndexing().subscribe(
+          result => {
+            expect(result).toEqual(activateMessage);
+          }, error => console.log(error));
+    })));
+
+    it('deactivateIndexing', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(deactivateResponse));
+
+      stormService.deactivateIndexing().subscribe(
+          result => {
+            expect(result).toEqual(deactivateMessage);
+          }, error => console.log(error));
+    })));
+
+    it('startIndexing', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(startResponse));
+
+      stormService.startIndexing().subscribe(
+          result => {
+            expect(result).toEqual(startMessage);
+          }, error => console.log(error));
+    })));
+
+    it('stopIndexing', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(stopResponse));
+
+      stormService.stopIndexing().subscribe(
+          result => {
+            expect(result).toEqual(stopMessage);
+          }, error => console.log(error));
+    })));
+
+    it('getStatus', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(broStatusResponse));
+
+      stormService.getStatus('bro').subscribe(
+          result => {
+            expect(result).toEqual(broStatus);
+          }, error => console.log(error));
+    })));
+
+    it('activateParser', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(activateResponse));
+
+      stormService.activateParser('bro').subscribe(
+          result => {
+            expect(result).toEqual(activateMessage);
+          }, error => console.log(error));
+    })));
+
+    it('deactivateParser', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(deactivateResponse));
+
+      stormService.deactivateParser('bro').subscribe(
+          result => {
+            expect(result).toEqual(deactivateMessage);
+          }, error => console.log(error));
+    })));
+
+    it('startParser', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(startResponse));
+
+      stormService.startParser('bro').subscribe(
+          result => {
+            expect(result).toEqual(startMessage);
+          }, error => console.log(error));
+    })));
+
+    it('stopParser', async(inject([], () => {
+      mockBackend.connections.subscribe((c: MockConnection) => 
c.mockRespond(stopResponse));
+
+      stormService.stopParser('bro').subscribe(
+          result => {
+            expect(result).toEqual(stopMessage);
+          }, error => console.log(error));
+    })));
+
+
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/storm.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/storm.service.ts 
b/metron-interface/metron-config/src/app/service/storm.service.ts
new file mode 100644
index 0000000..7a79e1f
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/storm.service.ts
@@ -0,0 +1,144 @@
+/**
+ * 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 {Injectable, Inject} from '@angular/core';
+import {Http, Headers, RequestOptions} from '@angular/http';
+import {HttpUtil} from '../util/httpUtil';
+import {TopologyStatus} from '../model/topology-status';
+import {TopologyResponse} from '../model/topology-response';
+import {APP_CONFIG} from '../app.config';
+import {IAppConfig} from '../app.config.interface';
+import {Observable} from 'rxjs/Observable';
+import 'rxjs/add/observable/interval';
+import 'rxjs/add/operator/switchMap';
+import 'rxjs/add/operator/onErrorResumeNext';
+
+@Injectable()
+export class StormService {
+  url = this.config.apiEndpoint + '/storm';
+  defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 
'XMLHttpRequest'};
+
+  constructor(private http: Http, @Inject(APP_CONFIG) private config: 
IAppConfig) {
+
+  }
+
+  public pollGetAll(): Observable<TopologyStatus[]> {
+    return Observable.interval(8000).switchMap(() => {
+      return this.http.get(this.url, new RequestOptions({headers: new 
Headers(this.defaultHeaders)}))
+          .map(HttpUtil.extractData)
+          .catch(HttpUtil.handleError)
+          .onErrorResumeNext();
+    });
+  }
+
+  public getAll(): Observable<TopologyStatus[]> {
+    return this.http.get(this.url, new RequestOptions({headers: new 
Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public getEnrichmentStatus(): Observable<TopologyStatus> {
+    return this.http.get(this.url + '/enrichment', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public activateEnrichment(): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/enrichment/activate', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public deactivateEnrichment(): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/enrichment/deactivate', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public startEnrichment(): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/enrichment/start', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public stopEnrichment(): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/enrichment/stop', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public getIndexingStatus(): Observable<TopologyStatus> {
+    return this.http.get(this.url + '/indexing', new RequestOptions({headers: 
new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public activateIndexing(): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/indexing/activate', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public deactivateIndexing(): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/indexing/deactivate', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public startIndexing(): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/indexing/start', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public stopIndexing(): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/indexing/stop', new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public getStatus(name: string): Observable<TopologyStatus> {
+    return this.http.get(this.url + '/' + name, new RequestOptions({headers: 
new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public activateParser(name: string): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/parser/activate/' + name, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public deactivateParser(name: string): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/parser/deactivate/' + name, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public startParser(name: string): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/parser/start/' + name, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+  public stopParser(name: string): Observable<TopologyResponse> {
+    return this.http.get(this.url + '/parser/stop/' + name, new 
RequestOptions({headers: new Headers(this.defaultHeaders)}))
+        .map(HttpUtil.extractData)
+        .catch(HttpUtil.handleError);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.html
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.html
 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.html
new file mode 100644
index 0000000..72489ed
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.html
@@ -0,0 +1,16 @@
+<!--
+  Licensed to the Apache Software
+       Foundation (ASF) under one or more contributor license agreements. See 
the
+       NOTICE file distributed with this work for additional information 
regarding
+       copyright ownership. The ASF licenses this file to You under the Apache 
License,
+       Version 2.0 (the "License"); you may not use this file except in 
compliance
+       with the License. You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software 
distributed
+       under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+       OR CONDITIONS OF ANY KIND, either express or implied. See the License 
for
+  the specific language governing permissions and limitations under the 
License.
+  -->
+<div #aceEditor class="editor"></div>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.scss
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.scss
 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.scss
new file mode 100644
index 0000000..3dbb13d
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.scss
@@ -0,0 +1,32 @@
+/**
+ * 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";
+.editor {
+  border-radius: 4px;
+  border: 1px solid #4d4d4d;
+}
+* /deep/ {
+  .ace_emptyMessage
+  {
+    @include place-holder-text;
+  }
+
+  .ace_gutter {
+    background: #272822 !important;
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.spec.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.spec.ts
 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.spec.ts
new file mode 100644
index 0000000..89f6205
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.spec.ts
@@ -0,0 +1,26 @@
+/**
+ * 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 { AceEditorComponent } from './ace-editor.component';
+
+describe('Component: AceEditor', () => {
+  it('should create an instance', () => {
+    let component = new AceEditorComponent();
+    expect(component).toBeTruthy();
+  });
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
new file mode 100644
index 0000000..d5edd83
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.component.ts
@@ -0,0 +1,182 @@
+import { Component, AfterViewInit, ViewChild, ElementRef, forwardRef, Input} 
from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+import Editor = AceAjax.Editor;
+import {AutocompleteOption} from '../../model/autocomplete-option';
+
+declare var ace: any;
+
+@Component({
+  selector: 'metron-config-ace-editor',
+  templateUrl: 'ace-editor.component.html',
+  styleUrls: ['ace-editor.component.scss'],
+  providers: [
+    {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => AceEditorComponent),
+      multi: true
+    }
+  ]
+})
+export class AceEditorComponent implements AfterViewInit, ControlValueAccessor 
{
+
+  inputJson: any = '';
+  aceConfigEditor: Editor;
+  @Input() type: string = 'JSON';
+  @Input() placeHolder: string = 'Enter text here';
+  @Input() options: AutocompleteOption[] = [];
+  @ViewChild('aceEditor') aceEditorEle: ElementRef;
+
+  private onTouchedCallback;
+  private onChangeCallback;
+
+  constructor() {
+    ace.config.set('basePath', '/assets/ace');
+  }
+
+  ngAfterViewInit() {
+    ace.config.loadModule('ace/ext/language_tools',  () => { 
this.initializeEditor(); });
+  }
+
+  writeValue(obj: any) {
+    this.inputJson = obj;
+    this.setInput();
+  }
+
+  registerOnChange(fn: any) {
+    this.onChangeCallback = fn;
+  }
+
+  registerOnTouched(fn: any) {
+    this.onTouchedCallback = fn;
+  }
+
+  setDisabledState(isDisabled: boolean) {
+    // TODO set readonly
+  }
+
+  initializeEditor() {
+    this.aceConfigEditor = this.createEditor(this.aceEditorEle.nativeElement);
+    this.addPlaceHolder();
+    this.setInput();
+  }
+
+  updatePlaceHolderText() {
+    let shouldShow = !this.aceConfigEditor.session.getValue().length;
+    let node = this.aceConfigEditor.renderer['emptyMessageNode'];
+    if (!shouldShow && node) {
+      
this.aceConfigEditor.renderer.scroller.removeChild(this.aceConfigEditor.renderer['emptyMessageNode']);
+      this.aceConfigEditor.renderer['emptyMessageNode'] = null;
+    } else if (shouldShow && !node) {
+      node = this.aceConfigEditor.renderer['emptyMessageNode'] = 
document.createElement('div');
+      node.textContent = this.placeHolder;
+      node.className = 'ace_invisible ace_emptyMessage';
+      this.aceConfigEditor.renderer.scroller.appendChild(node);
+    }
+  }
+
+  addPlaceHolder() {
+    this.aceConfigEditor.on('input', () => { this.updatePlaceHolderText(); });
+    setTimeout(() => { this.updatePlaceHolderText(); }, 100);
+  }
+
+  private createEditor(element: ElementRef) {
+    let parserConfigEditor = ace.edit(element);
+    parserConfigEditor.getSession().setMode(this.getEditorType());
+    parserConfigEditor.getSession().setTabSize(2);
+    parserConfigEditor.getSession().setUseWrapMode(true);
+    parserConfigEditor.getSession().setWrapLimitRange(72, 72);
+
+    parserConfigEditor.$blockScrolling = Infinity;
+    parserConfigEditor.setTheme('ace/theme/monokai');
+    parserConfigEditor.setOptions({
+      minLines: 10,
+      highlightActiveLine: false,
+      maxLines: Infinity,
+      enableBasicAutocompletion: true,
+      enableSnippets: true,
+      enableLiveAutocompletion: true
+    });
+    parserConfigEditor.on('change', (e: any) => {
+      this.inputJson = this.aceConfigEditor.getValue();
+      this.onChangeCallback(this.aceConfigEditor.getValue());
+    });
+
+    if (this.type === 'GROK') {
+      parserConfigEditor.completers = [this.getGrokCompletion()];
+    }
+
+    return parserConfigEditor;
+  }
+
+  private getGrokCompletion() {
+    let _this = this;
+    return {
+      getCompletions: function(editor, session, pos, prefix, callback) {
+        let autoCompletePrefix = '';
+        let autoCompleteSuffix = '';
+        let options = _this.options;
+
+        let currentToken = editor.getSession().getTokenAt(pos.row, pos.column);
+
+        // No value or user typed just a char
+        if (currentToken === null || currentToken.type === 'comment') {
+          autoCompletePrefix = '%{';
+          autoCompleteSuffix = ':$0}';
+        } else {
+          // }any<here>
+          if (currentToken.type === 'invalid') {
+            let lastToken = editor.getSession().getTokenAt(pos.row, 
(pos.column - currentToken.value.length));
+            autoCompletePrefix = lastToken.value.endsWith('}') ? ' %{' : '%{';
+            autoCompleteSuffix = ':$0}';
+          }
+
+          // In %{<here>}
+          if (currentToken.type === 'paren.rparen') {
+            autoCompletePrefix = currentToken.value.endsWith(' ') ? '%{' : ' 
%{';
+            autoCompleteSuffix = ':$0}';
+          }
+
+          // %{NUM<here>:}
+          if (currentToken.type === 'paren.lparen' || currentToken.type === 
'variable') {
+            let nextToken = editor.getSession().getTokenAt(pos.row, pos.column 
+ 1);
+            autoCompletePrefix = '';
+            autoCompleteSuffix = (nextToken && nextToken.value.indexOf(':') > 
-1) ? '' : ':$0}';
+          }
+
+          // %{NUMBER:<here>}
+          if (currentToken.type === 'seperator' || currentToken.type === 
'string') {
+            let autocompleteVal = currentToken.value.replace(/:/g, '');
+            let autocompletes = autocompleteVal.length === 0 ? 'variable' : '';
+            options = [new AutocompleteOption(autocompletes)];
+          }
+        }
+
+        callback(null, options.map(function(autocompleteOption) {
+          return {
+            caption: autocompleteOption.name,
+            snippet: autoCompletePrefix + autocompleteOption.name + 
autoCompleteSuffix,
+            meta: 'grok-pattern',
+            score: Number.MAX_VALUE
+          };
+        }));
+
+      }
+    };
+  }
+
+  private getEditorType() {
+      if (this.type === 'GROK') {
+        return 'ace/mode/grok';
+      }
+
+      return 'ace/mode/json';
+  }
+
+  private setInput() {
+      if (this.aceConfigEditor && this.inputJson) {
+        this.aceConfigEditor.getSession().setValue(this.inputJson);
+        this.aceConfigEditor.resize(true);
+      }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.module.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.module.ts 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.module.ts
new file mode 100644
index 0000000..56817da
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/ace-editor/ace-editor.module.ts
@@ -0,0 +1,12 @@
+import {NgModule} from '@angular/core';
+
+import {AceEditorComponent}   from './ace-editor.component';
+
+@NgModule({
+    imports: [],
+    exports: [AceEditorComponent],
+    declarations: [AceEditorComponent],
+    providers: [],
+})
+export class AceEditorModule {
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/ace-editor/index.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/ace-editor/index.ts 
b/metron-interface/metron-config/src/app/shared/ace-editor/index.ts
new file mode 100644
index 0000000..a8ba0f0
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/ace-editor/index.ts
@@ -0,0 +1,18 @@
+/**
+ * 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.
+ */
+export * from './ace-editor.component'

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.html
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.html
 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.html
new file mode 100644
index 0000000..1a30ee7
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.html
@@ -0,0 +1,39 @@
+<!--
+  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.
+  -->
+<form role="form" [formGroup]="configForm">
+    <div *ngFor="let key of configKeys">
+        <div class="row mx-0 configkey-row">
+            <div class="col-md-10 advanced-input"><input type="text" 
class="form-control" name="advanced1" value="{{key}}" readonly></div>
+            <div class="col-md-2">
+            </div>
+        </div>
+        <div class="row  mx-0">
+            <div class="col-md-10 advanced-input"><input type="text" 
class="form-control" formControlName="{{key}}" [(ngModel)]="config[key]"></div>
+            <div class="col-md-2" (click)="removeConfig(key)">
+                <i class="fa fa-minus fa-4 icon-button" aria-hidden="true" 
></i>
+            </div>
+        </div>
+    </div>
+    <div class="row  mx-0 new-configkey-row">
+        <div class="col-md-10 advanced-input"><input type="text" 
class="form-control"  (focus)="clearKeyPlaceholder()" (blur)="saveNewConfig()" 
[ngClass]="{'input-placeholder': newConfigKey == 'enter field'}" 
formControlName="newConfigKey" [(ngModel)]="newConfigKey"></div>
+        <div class="col-md-2">
+        </div>
+    </div>
+    <div class="row  mx-0">
+        <div class="col-md-10 advanced-input"><input type="text" 
class="form-control" (focus)="clearValuePlaceholder()" (blur)="saveNewConfig()" 
[ngClass]="{'input-placeholder': newConfigValue == 'enter value'}" 
formControlName="newConfigValue" [(ngModel)]="newConfigValue"></div>
+        <div class="col-md-2" (click)="addConfig()">
+            <i class="fa fa-plus fa-4 icon-button" aria-hidden="true" ></i>
+        </div>
+    </div>
+</form>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.scss
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.scss
 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.scss
new file mode 100644
index 0000000..c43f0b1
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.scss
@@ -0,0 +1,61 @@
+/**
+ * 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";
+
+.advanced {
+  padding-left: 10px;
+  cursor: pointer;
+}
+
+.advanced-input {
+  padding-left: 0;
+  padding-right: 5px
+}
+
+.advanced-link {
+  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;
+}
+
+.new-configkey-row {
+  margin-bottom: 5px;
+  padding-top: 10px
+}
+
+.configkey-row {
+  margin-bottom: 5px;
+  margin-top: 10px
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.spec.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.spec.ts
 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.spec.ts
new file mode 100644
index 0000000..a8f0ed0
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.spec.ts
@@ -0,0 +1,151 @@
+/**
+ * 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 {FormsModule, ReactiveFormsModule} from '@angular/forms';
+import {AdvancedConfigFormComponent} from './advanced-config-form.component';
+
+describe('Component: AdvancedConfigFormComponent', () => {
+
+  let comp: AdvancedConfigFormComponent;
+  let fixture: ComponentFixture<AdvancedConfigFormComponent>;
+
+  beforeEach(async(() => {
+
+    TestBed.configureTestingModule({
+      imports: [FormsModule, ReactiveFormsModule],
+      declarations: [AdvancedConfigFormComponent]
+    }).compileComponents()
+      .then(() => {
+        fixture = TestBed.createComponent(AdvancedConfigFormComponent);
+        comp = fixture.componentInstance;
+      });
+
+  }));
+
+  it('should create new forms for AdvancedConfigFormComponent',  async(() => {
+      let component: AdvancedConfigFormComponent =  fixture.componentInstance;
+      component.config = {'field1': 'value1', 'field2': 'value2'};
+      component.ngOnInit();
+
+      expect(Object.keys(component.configForm.controls).length).toEqual(4);
+      
expect(component.configForm.controls['newConfigKey'].value).toEqual('enter 
field');
+      
expect(component.configForm.controls['newConfigValue'].value).toEqual('enter 
value');
+      expect(component.configForm.controls['field1'].value).toEqual('value1');
+      expect(component.configForm.controls['field2'].value).toEqual('value2');
+  }));
+
+  it('OnChanges should recreate the form',  async(() => {
+    let component: AdvancedConfigFormComponent =  fixture.componentInstance;
+    component.config = {'field1': 'value1', 'field2': 'value2'};
+    let changes = {'field1': {'currentValue' : 'value1', 'previousValue': 
'value1'}};
+    spyOn(component, 'createForm');
+
+    component.ngOnChanges(changes);
+
+    expect(component.createForm).not.toHaveBeenCalled();
+    expect(component.configKeys).toEqual([]);
+
+    changes = {'field1': {'currentValue' : 'value1', 'previousValue': 
'value-1-1'}};
+    component.ngOnChanges(changes);
+
+    expect(component.createForm).toHaveBeenCalled();
+    expect(component.configKeys).toEqual(['field1', 'field2']);
+
+  }));
+
+  it('verify form interactions AdvancedConfigFormComponent',  async(() => {
+      let component: AdvancedConfigFormComponent =  fixture.componentInstance;
+      component.config = {'field1': 'value1', 'field2': 'value2'};
+      component.ngOnInit();
+
+
+      expect(component.newConfigKey).toEqual('enter field');
+      expect(component.newConfigValue).toEqual('enter value');
+      expect(component.configKeys).toEqual(['field1', 'field2']);
+
+      component.clearKeyPlaceholder();
+      expect(component.newConfigKey).toEqual('');
+      component.clearValuePlaceholder();
+      expect(component.newConfigValue).toEqual('');
+
+      component.newConfigKey = '';
+      component.newConfigValue = '';
+      component.saveNewConfig();
+      expect(Object.keys(component.config).length).toEqual(2);
+      expect(component.config['field1']).toEqual('value1');
+      expect(component.config['field2']).toEqual('value2');
+      expect(component.newConfigKey).toEqual('enter field');
+      expect(component.newConfigValue).toEqual('enter value');
+      component.addConfig();
+      expect(component.configKeys).toEqual(['field1', 'field2']);
+      expect(Object.keys(component.configForm.controls).length).toEqual(4);
+      
expect(component.configForm.controls['newConfigKey'].value).toEqual('enter 
field');
+      
expect(component.configForm.controls['newConfigValue'].value).toEqual('enter 
value');
+      expect(component.configForm.controls['field1'].value).toEqual('value1');
+      expect(component.configForm.controls['field2'].value).toEqual('value2');
+
+
+      component.newConfigKey = 'field3';
+      component.newConfigValue = 'value3';
+      component.saveNewConfig();
+      expect(Object.keys(component.config).length).toEqual(3);
+      expect(component.config['field1']).toEqual('value1');
+      expect(component.config['field2']).toEqual('value2');
+      expect(component.config['field3']).toEqual('value3');
+      expect(component.newConfigKey).toEqual('field3');
+      expect(component.newConfigValue).toEqual('value3');
+      component.addConfig();
+      expect(component.configKeys).toEqual(['field1', 'field2', 'field3']);
+      expect(Object.keys(component.configForm.controls).length).toEqual(5);
+      
expect(component.configForm.controls['newConfigKey'].value).toEqual('enter 
field');
+      
expect(component.configForm.controls['newConfigValue'].value).toEqual('enter 
value');
+      expect(component.configForm.controls['field1'].value).toEqual('value1');
+      expect(component.configForm.controls['field2'].value).toEqual('value2');
+      expect(component.configForm.controls['field3'].value).toEqual('value3');
+
+      component.newConfigKey = 'field1';
+      component.newConfigValue = 'newValue1';
+      component.saveNewConfig();
+      expect(Object.keys(component.config).length).toEqual(3);
+      expect(component.config['field1']).toEqual('newValue1');
+      expect(component.config['field2']).toEqual('value2');
+      expect(component.config['field3']).toEqual('value3');
+      expect(component.newConfigKey).toEqual('enter field');
+      expect(component.newConfigValue).toEqual('enter value');
+      component.addConfig();
+      expect(component.configKeys).toEqual(['field1', 'field2', 'field3']);
+      expect(Object.keys(component.configForm.controls).length).toEqual(5);
+      
expect(component.configForm.controls['newConfigKey'].value).toEqual('enter 
field');
+      
expect(component.configForm.controls['newConfigValue'].value).toEqual('enter 
value');
+      expect(component.configForm.controls['field1'].value).toEqual('value1');
+      expect(component.configForm.controls['field2'].value).toEqual('value2');
+      expect(component.configForm.controls['field3'].value).toEqual('value3');
+
+      component.removeConfig('field1');
+      expect(Object.keys(component.config).length).toEqual(2);
+      expect(component.config['field2']).toEqual('value2');
+      expect(component.config['field3']).toEqual('value3');
+      expect(component.configKeys).toEqual(['field2', 'field3']);
+      expect(Object.keys(component.configForm.controls).length).toEqual(4);
+      
expect(component.configForm.controls['newConfigKey'].value).toEqual('enter 
field');
+      
expect(component.configForm.controls['newConfigValue'].value).toEqual('enter 
value');
+      expect(component.configForm.controls['field2'].value).toEqual('value2');
+      expect(component.configForm.controls['field3'].value).toEqual('value3');
+  }));
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.ts
 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.ts
new file mode 100644
index 0000000..f363391
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.component.ts
@@ -0,0 +1,110 @@
+/**
+ * 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, OnChanges, Input} from '@angular/core';
+import {FormGroup, Validators, FormControl} from '@angular/forms';
+
+@Component({
+  selector: 'metron-config-advanced-form',
+  templateUrl: 'advanced-config-form.component.html',
+  styleUrls: ['advanced-config-form.component.scss']
+})
+export class AdvancedConfigFormComponent implements OnInit, OnChanges {
+
+  @Input() config: {};
+  configKeys: string[] = [];
+  newConfigKey: string = 'enter field';
+  newConfigValue: string = 'enter value';
+  configForm: FormGroup;
+
+  constructor() {
+
+  }
+
+  ngOnInit() {
+    this.configKeys = Object.keys(this.config);
+    this.configForm = this.createForm();
+  }
+
+  ngOnChanges(changes: {[propertyName: string]: any}) {
+    for (let propName of Object.keys(changes)) {
+      let chng = changes[propName];
+      let cur = JSON.stringify(chng.currentValue);
+      let prev = JSON.stringify(chng.previousValue);
+      if (cur !== prev) {
+        this.configKeys = Object.keys(this.config);
+        this.configForm = this.createForm();
+      }
+    }
+  }
+
+  createForm(): FormGroup {
+    let group: any = {};
+    for (let key of this.configKeys) {
+      group[key] = new FormControl(this.config[key], Validators.required);
+    }
+    group['newConfigKey'] = new FormControl(this.newConfigKey, 
Validators.required);
+    group['newConfigValue'] = new FormControl(this.newConfigValue, 
Validators.required);
+    return new FormGroup(group);
+  }
+
+  clearKeyPlaceholder() {
+    if (this.newConfigKey === 'enter field') {
+      this.newConfigKey = '';
+    }
+  }
+
+  clearValuePlaceholder() {
+    if (this.newConfigValue === 'enter value') {
+      this.newConfigValue = '';
+    }
+  }
+
+  saveNewConfig() {
+    if (this.newConfigKey === '') {
+      this.newConfigKey = 'enter field';
+    }
+    if (this.newConfigValue === '') {
+      this.newConfigValue = 'enter value';
+    }
+    if (this.newConfigKey !== 'enter field' && this.newConfigValue !== 'enter 
value') {
+      let keyExists = this.config[this.newConfigKey] !== undefined;
+      this.config[this.newConfigKey] = this.newConfigValue;
+      if (keyExists) {
+        this.newConfigKey = 'enter field';
+        this.newConfigValue = 'enter value';
+      }
+
+    }
+  }
+
+  addConfig() {
+    if (this.newConfigKey !== 'enter field' && this.newConfigValue !== 'enter 
value') {
+      this.configKeys.push(this.newConfigKey);
+      this.configForm.addControl(this.newConfigKey, new 
FormControl(this.newConfigValue, Validators.required));
+      this.newConfigKey = 'enter field';
+      this.newConfigValue = 'enter value';
+    }
+  }
+
+  removeConfig(key: string) {
+    delete this.config[key];
+    this.configKeys = Object.keys(this.config);
+    this.configForm.removeControl(key);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.module.ts
----------------------------------------------------------------------
diff --git 
a/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.module.ts
 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.module.ts
new file mode 100644
index 0000000..d29a369
--- /dev/null
+++ 
b/metron-interface/metron-config/src/app/shared/advanced-config-form/advanced-config-form.module.ts
@@ -0,0 +1,14 @@
+import {NgModule} from '@angular/core';
+
+import {AdvancedConfigFormComponent}   from './advanced-config-form.component';
+import {SharedModule} from '../shared.module';
+import {ReactiveFormsModule} from '@angular/forms';
+
+@NgModule({
+    imports: [ ReactiveFormsModule, SharedModule ],
+    exports: [ AdvancedConfigFormComponent ],
+    declarations: [ AdvancedConfigFormComponent ],
+    providers: [],
+})
+export class AdvancedConfigFormModule {
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts 
b/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts
new file mode 100644
index 0000000..78c246a
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/auth-guard.spec.ts
@@ -0,0 +1,92 @@
+/**
+ * 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, inject, TestBed} from '@angular/core/testing';
+import {EventEmitter}     from '@angular/core';
+import {AuthGuard} from './auth-guard';
+import {AuthenticationService} from '../service/authentication.service';
+import {Router} from '@angular/router';
+
+class MockAuthenticationService {
+  _isAuthenticationChecked: boolean;
+  _isAuthenticated: boolean;
+  onLoginEvent: EventEmitter<boolean> = new EventEmitter<boolean>();
+
+  public isAuthenticationChecked(): boolean {
+    return this._isAuthenticationChecked;
+  }
+
+  public isAuthenticated(): boolean {
+    return this._isAuthenticated;
+  }
+}
+
+class MockRouter {
+  navigateByUrl(): any {}
+}
+
+describe('AuthGuard', () => {
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      providers: [
+        AuthGuard,
+        {provide: AuthenticationService, useClass: MockAuthenticationService},
+        {provide: Router, useClass: MockRouter}
+      ]
+    })
+      .compileComponents();
+
+  }));
+
+  it('can instantiate auth guard',
+    inject([AuthGuard], (authGuard: AuthGuard) => {
+      expect(authGuard instanceof AuthGuard).toBe(true);
+    }));
+
+  it('test when authentication is checked',
+    inject([AuthGuard, AuthenticationService], (authGuard: AuthGuard, 
authenticationService: MockAuthenticationService) => {
+      authenticationService._isAuthenticationChecked = true;
+      authenticationService._isAuthenticated = true;
+      expect(authGuard.canActivate(null, null)).toBe(true);
+
+      authenticationService._isAuthenticationChecked = true;
+      authenticationService._isAuthenticated = false;
+      expect(authGuard.canActivate(null, null)).toBe(false);
+    }));
+
+  it('test when authentication is not checked',
+    inject([AuthGuard, AuthenticationService, Router], (authGuard: AuthGuard,
+                                                        authenticationService: 
MockAuthenticationService,
+                                                        router: MockRouter) => 
{
+      authenticationService._isAuthenticationChecked = false;
+      authGuard.canActivate(null, null).subscribe(isUserValid => {
+        expect(isUserValid).toBe(true);
+      });
+      authenticationService.onLoginEvent.emit(true);
+
+
+      spyOn(router, 'navigateByUrl');
+      authenticationService._isAuthenticationChecked = false;
+      authGuard.canActivate(null, null).subscribe(isUserValid => {
+        expect(isUserValid).toBe(false);
+      });
+      authenticationService.onLoginEvent.emit(false);
+      expect(router.navigateByUrl).toHaveBeenCalledWith('/login');
+
+    }));
+});

Reply via email to