metron-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rmerri...@apache.org
Subject [03/12] incubator-metron git commit: METRON-623 Management UI [contributed by Raghu Mitra Kandikonda and Ryan Merriman] closes apache/incubator-metron#489
Date Tue, 11 Apr 2017 13:51:14 GMT
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/auth-guard.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/auth-guard.ts b/metron-interface/metron-config/src/app/shared/auth-guard.ts
new file mode 100644
index 0000000..66c27cf
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/auth-guard.ts
@@ -0,0 +1,50 @@
+/**
+ * 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 } from '@angular/core';
+import {
+  CanActivate,
+  Router,
+  ActivatedRouteSnapshot,
+  RouterStateSnapshot
+} from '@angular/router';
+import { Observable } from 'rxjs/Observable';
+import { AuthenticationService } from '../service/authentication.service';
+
+@Injectable()
+export class AuthGuard implements CanActivate {
+
+  constructor(private authService: AuthenticationService, private router: Router) {}
+
+  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+    if (!this.authService.isAuthenticationChecked()) {
+      return Observable.create(observer => {
+        this.authService.onLoginEvent.subscribe(isLoggedIn => {
+          if (isLoggedIn) {
+            observer.next(true);
+            observer.complete();
+          } else {
+            observer.next(false);
+            observer.complete();
+            this.router.navigateByUrl('/login');
+          }
+        });
+      });
+    }
+    return this.authService.isAuthenticated();
+  }
+}

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

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/login-guard.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/login-guard.spec.ts b/metron-interface/metron-config/src/app/shared/login-guard.spec.ts
new file mode 100644
index 0000000..a49b124
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/login-guard.spec.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 {async, inject, TestBed} from '@angular/core/testing';
+import {AuthenticationService} from '../service/authentication.service';
+import {Router} from '@angular/router';
+import {LoginGuard} from './login-guard';
+
+class MockAuthenticationService {
+  public logout(): void {}
+}
+
+class MockRouter {
+  navigateByUrl(): any {}
+}
+
+describe('LoginGuard', () => {
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      providers: [
+        LoginGuard,
+        {provide: AuthenticationService, useClass: MockAuthenticationService},
+        {provide: Router, useClass: MockRouter}
+      ]
+    })
+      .compileComponents();
+
+  }));
+
+  it('can instantiate auth guard',
+    inject([LoginGuard], (loginGaurd: LoginGuard) => {
+      expect(loginGaurd instanceof LoginGuard).toBe(true);
+  }));
+
+  it('test when login is checked',
+    inject([LoginGuard, AuthenticationService], (loginGuard: LoginGuard, authenticationService: MockAuthenticationService) => {
+
+      spyOn(authenticationService, 'logout');
+
+      expect(loginGuard.canActivate(null, null)).toBe(true);
+
+      expect(authenticationService.logout).toHaveBeenCalled();
+
+  }));
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/login-guard.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/login-guard.ts b/metron-interface/metron-config/src/app/shared/login-guard.ts
new file mode 100644
index 0000000..d8b8f0d
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/login-guard.ts
@@ -0,0 +1,40 @@
+/**
+ * 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 } from '@angular/core';
+import {
+  CanActivate,
+  Router,
+  ActivatedRouteSnapshot,
+  RouterStateSnapshot
+} from '@angular/router';
+import { AuthenticationService } from '../service/authentication.service';
+
+// This guard will ensure that logout is called even if you call /login manually
+@Injectable()
+export class LoginGuard implements CanActivate {
+
+  constructor(private authService: AuthenticationService, private router: Router) {}
+
+  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
+
+    this.authService.logout();
+
+    return true;
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-alerts.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-alerts.spec.ts b/metron-interface/metron-config/src/app/shared/metron-alerts.spec.ts
new file mode 100644
index 0000000..5efe063
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-alerts.spec.ts
@@ -0,0 +1,75 @@
+/**
+ * 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.
+ */
+/* tslint:disable:no-unused-variable */
+
+import {MetronAlerts} from './metron-alerts';
+
+describe('MetronAlerts', () => {
+
+  beforeEach(function() {
+    MetronAlerts.SUCESS_MESSAGE_DISPALY_TIME = 500;
+  });
+
+  afterEach(function() {
+    MetronAlerts.SUCESS_MESSAGE_DISPALY_TIME = 5000;
+  });
+
+  it('should create an instance', () => {
+    expect(new MetronAlerts()).toBeTruthy();
+  });
+
+  it('should close success message after timeout', (done) => {
+    new MetronAlerts().showSuccessMessage('test message');
+
+    expect($(document).find('.alert.alert-success span').text()).toEqual('test message');
+
+    setTimeout(() => {
+      expect($(document).find('.alert .alert-success').length).toEqual(0);
+      done();
+    }, MetronAlerts.SUCESS_MESSAGE_DISPALY_TIME);
+  });
+
+  it('should close success message on click of close', (done) => {
+    new MetronAlerts().showSuccessMessage('test message');
+
+    expect($(document).find('.alert.alert-success span').text()).toEqual('test message');
+
+    $(document).find('.alert.alert-success .close').click();
+
+    expect($(document).find('.alert .alert-success').length).toEqual(0);
+
+    setTimeout(() => {
+      expect($(document).find('.alert .alert-success').length).toEqual(0);
+      done();
+    }, MetronAlerts.SUCESS_MESSAGE_DISPALY_TIME);
+
+  });
+
+  // it('should close error message on click of close', () => {
+  //   new MetronAlerts().showErrorMessage('test message');
+  //
+  //   expect($(document).find('.alert.alert-danger span').text()).toEqual('test message');
+  //
+  //   $(document).find('.alert.alert-danger .close').click();
+  //
+  //   expect($(document).find('.alert .alert-danger').length).toEqual(0);
+  //
+  // });
+
+});
+

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-alerts.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-alerts.ts b/metron-interface/metron-config/src/app/shared/metron-alerts.ts
new file mode 100644
index 0000000..5b307e4
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-alerts.ts
@@ -0,0 +1,52 @@
+/**
+ * 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 class MetronAlerts {
+
+  public static SUCESS_MESSAGE_DISPALY_TIME = 5000;
+
+  private createMessage(message: string, type: string): Node {
+    let element = document.createElement('div');
+    element.id = 'alert_placeholder';
+    element.style.zIndex = '1000';
+    element.style.position = 'fixed';
+    element.style.display = 'inline-block';
+    element.style.minWidth = '250px';
+    element.style.cssFloat = 'right';
+    element.style.top = '5px';
+    element.style.right = '35px';
+
+    element.innerHTML = '<div id="alertdiv" class="alert ' + type + '"><a class="close" data-dismiss="alert">×</a><span>' +
+      message + '</span></div>';
+    document.body.appendChild(element);
+
+    return element;
+  }
+
+  showErrorMessage(message: string): void {
+    this.createMessage(message, 'alert-danger');
+  }
+
+  showSuccessMessage(message: string): void {
+    let element = this.createMessage(message, 'alert-success');
+    setTimeout(function () {
+
+      document.body.removeChild(element);
+
+    }, MetronAlerts.SUCESS_MESSAGE_DISPALY_TIME);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-dialog-box.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-dialog-box.spec.ts b/metron-interface/metron-config/src/app/shared/metron-dialog-box.spec.ts
new file mode 100644
index 0000000..3c4acb7
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-dialog-box.spec.ts
@@ -0,0 +1,63 @@
+/**
+ * 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.
+ */
+/* tslint:disable:no-unused-variable */
+
+import {MetronDialogBox} from './metron-dialog-box';
+
+describe('MetronDialogBox', () => {
+
+  it('should create an instance', () => {
+    expect(new MetronDialogBox()).toBeTruthy();
+  });
+
+  it('should return true', () => {
+    let showConfirmationMessage = new MetronDialogBox().showConfirmationMessage('test message');
+    let subscription = showConfirmationMessage.subscribe(result => {
+      expect(result).toEqual(true);
+      subscription.unsubscribe();
+    });
+    spyOn(showConfirmationMessage, 'subscribe');
+
+    expect($(document).find('.metron-dialog .modal-title').text()).toEqual('Confirmation');
+    expect($(document).find('.metron-dialog .modal-body p').text()).toEqual('test message');
+
+    $(document).find('.metron-dialog .btn-primary').click();
+
+    $(document).find('.metron-dialog').trigger('hidden.bs.modal');
+
+    expect($(document).find('.metron-dialog').length).toEqual(0);
+
+  });
+
+  it('should return false', () => {
+    let showConfirmationMessage = new MetronDialogBox().showConfirmationMessage('test message');
+    let subscription = showConfirmationMessage.subscribe(result => {
+      expect(result).toEqual(false);
+      subscription.unsubscribe();
+    });
+    spyOn(showConfirmationMessage, 'subscribe');
+
+    $(document).find('.metron-dialog .form-enable-disable-button').click();
+
+    $(document).find('.metron-dialog').trigger('hidden.bs.modal');
+
+    expect($(document).find('.metron-dialog').length).toEqual(0);
+
+  });
+});
+

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-dialog-box.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-dialog-box.ts b/metron-interface/metron-config/src/app/shared/metron-dialog-box.ts
new file mode 100644
index 0000000..5e0e32b
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-dialog-box.ts
@@ -0,0 +1,75 @@
+/**
+ * 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 {EventEmitter}     from '@angular/core';
+
+export class MetronDialogBox {
+
+  private createDialogBox(message: string, title: string) {
+
+    let html = `<div class="metron-dialog modal fade"  data-backdrop="static" >
+                  <div class="modal-dialog modal-sm" role="document">
+                    <div class="modal-content">
+                      <div class="modal-header">
+                        <button type="button" class="close" data-dismiss="modal" aria-label="Close"> 
+                            <span aria-hidden="true">&times;</span> 
+                        </button>
+                        <span class="modal-title"><b>` + title + `</b></span>
+                      </div>
+                      <div class="modal-body">
+                        <p>` +  message + `</p>
+                      </div>
+                      <div class="modal-footer">
+                        <button type="button" class="btn btn-primary">OK</button>
+                        <button type="button" class="btn form-enable-disable-button" data-dismiss="modal">Cancel</button>
+                      </div>
+                    </div>
+                  </div>
+                </div>`;
+
+    let element = document.createElement('div');
+    element.innerHTML = html;
+
+    document.body.appendChild(element);
+
+    return element;
+  }
+
+  public showConfirmationMessage(message: string): EventEmitter<boolean> {
+    let eventEmitter = new EventEmitter<boolean>();
+    let element = this.createDialogBox(message, 'Confirmation');
+
+    $(element).find('.metron-dialog').modal('show');
+
+    $(element).find('.btn-primary').on('click', function (e) {
+      $(element).find('.metron-dialog').modal('hide');
+      eventEmitter.emit(true);
+    });
+
+    $(element).find('.form-enable-disable-button').on('click', function (e) {
+      $(element).find('.metron-dialog').modal('hide');
+      eventEmitter.emit(false);
+    });
+
+    $(element).find('.metron-dialog').on('hidden.bs.modal', function (e) {
+      $(element).remove();
+    });
+
+    return eventEmitter;
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-modal/index.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-modal/index.ts b/metron-interface/metron-config/src/app/shared/metron-modal/index.ts
new file mode 100644
index 0000000..ec19caf
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-modal/index.ts
@@ -0,0 +1 @@
+export * from './metron-modal.component';

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.html b/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.html
new file mode 100644
index 0000000..3a0b18e
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.html
@@ -0,0 +1,20 @@
+<!--
+  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 [ngClass]="{'metron-modal': backgroundMasked, 'metron-modal-clickable': !backgroundMasked}"  data-backdrop="static" data-keyboard="false">
+  <div class="dialog-pane">
+    <ng-content></ng-content>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.scss b/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.scss
new file mode 100644
index 0000000..b47468f
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.scss
@@ -0,0 +1,45 @@
+/**
+ * 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 "../../../styles.scss";
+
+.metron-modal {
+  position: fixed;
+  z-index: 1;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgb(0,0,0);
+  background-color: rgba(0,0,0,0.4);
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.metron-modal-clickable {
+  position: fixed;
+  z-index: 1;
+  right: 0;
+  top: 0;
+  height: 100%;
+  padding-left: 15px;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+.dialog-pane {
+  @extend .flexbox-row-reverse;
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.spec.ts b/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.spec.ts
new file mode 100644
index 0000000..74f6c1e
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.spec.ts
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {async, TestBed, ComponentFixture} from '@angular/core/testing';
+import {SharedModule} from '../shared.module';
+import { MetronModalComponent } from './metron-modal.component';
+
+describe('MetronModalComponent', () => {
+
+  let fixture: ComponentFixture<MetronModalComponent>;
+  let component: MetronModalComponent;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [SharedModule],
+      providers: [
+        MetronModalComponent
+      ]
+    });
+
+    fixture = TestBed.createComponent(MetronModalComponent);
+    component = fixture.componentInstance;
+
+  }));
+
+  it('can instantiate MetronModalComponent', async(() => {
+    expect(component instanceof MetronModalComponent).toBe(true);
+  }));
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.ts b/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.ts
new file mode 100644
index 0000000..22a95d6
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-modal/metron-modal.component.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 { Component, Input } from '@angular/core';
+
+@Component({
+  selector: 'metron-config-metron-modal',
+  templateUrl: 'metron-modal.component.html',
+  styleUrls: ['metron-modal.component.scss']
+})
+export class MetronModalComponent {
+  @Input() backgroundMasked = true;
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/index.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/index.ts b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/index.ts
new file mode 100644
index 0000000..325568a
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/index.ts
@@ -0,0 +1 @@
+export * from './metron-sorter.component';

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.html b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.html
new file mode 100644
index 0000000..988bec1
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.html
@@ -0,0 +1,21 @@
+<!--
+  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.
+  -->
+<a style="cursor: pointer"  class="text-nowrap" (click)="sort()">
+  <ng-content></ng-content>
+  <i class="fa fa-sort" aria-hidden="true" *ngIf="!sortAsc && !sortDesc"></i>
+  <i class="fa fa-sort-asc" aria-hidden="true" *ngIf="sortAsc"></i>
+  <i class="fa fa-sort-desc" aria-hidden="true" *ngIf="sortDesc"></i>
+</a>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.scss b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.scss
new file mode 100644
index 0000000..5d5f1e3
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.scss
@@ -0,0 +1,17 @@
+/**
+ * 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.
+ */

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.spec.ts b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.spec.ts
new file mode 100644
index 0000000..cac8667
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.spec.ts
@@ -0,0 +1,75 @@
+/* tslint:disable:no-unused-variable */
+// directiveSelectorNameRule
+
+import {MetronSorterComponent} from './metron-sorter.component';
+import {MetronTableDirective} from '../metron-table.directive';
+
+describe('Component: MetronSorter', () => {
+
+  it('should create an instance', () => {
+    let metronTable = new MetronTableDirective();
+    let component = new MetronSorterComponent(metronTable);
+    expect(component).toBeTruthy();
+  });
+
+  it('should set the variables according to sorter', () => {
+    let metronTable = new MetronTableDirective();
+    let sorter1 = new MetronSorterComponent(metronTable);
+    let sorter2 = new MetronSorterComponent(metronTable);
+    let sorter3 = new MetronSorterComponent(metronTable);
+
+    sorter1.sortBy = 'col1';
+    sorter2.sortBy = 'col2';
+    sorter3.sortBy = 'col3';
+
+    sorter1.sort();
+    expect(sorter1.sortAsc).toEqual(true);
+    expect(sorter1.sortDesc).toEqual(false);
+    expect(sorter2.sortAsc).toEqual(false);
+    expect(sorter2.sortDesc).toEqual(false);
+    expect(sorter3.sortAsc).toEqual(false);
+    expect(sorter3.sortDesc).toEqual(false);
+
+    sorter1.sort();
+    expect(sorter1.sortAsc).toEqual(false);
+    expect(sorter1.sortDesc).toEqual(true);
+    expect(sorter2.sortAsc).toEqual(false);
+    expect(sorter2.sortDesc).toEqual(false);
+    expect(sorter3.sortAsc).toEqual(false);
+    expect(sorter3.sortDesc).toEqual(false);
+
+    sorter2.sort();
+    expect(sorter1.sortAsc).toEqual(false);
+    expect(sorter1.sortDesc).toEqual(false);
+    expect(sorter2.sortAsc).toEqual(true);
+    expect(sorter2.sortDesc).toEqual(false);
+    expect(sorter3.sortAsc).toEqual(false);
+    expect(sorter3.sortDesc).toEqual(false);
+
+    sorter2.sort();
+    expect(sorter1.sortAsc).toEqual(false);
+    expect(sorter1.sortDesc).toEqual(false);
+    expect(sorter2.sortAsc).toEqual(false);
+    expect(sorter2.sortDesc).toEqual(true);
+    expect(sorter3.sortAsc).toEqual(false);
+    expect(sorter3.sortDesc).toEqual(false);
+
+    sorter3.sort();
+    expect(sorter1.sortAsc).toEqual(false);
+    expect(sorter1.sortDesc).toEqual(false);
+    expect(sorter2.sortAsc).toEqual(false);
+    expect(sorter2.sortDesc).toEqual(false);
+    expect(sorter3.sortAsc).toEqual(true);
+    expect(sorter3.sortDesc).toEqual(false);
+
+    sorter3.sort();
+    expect(sorter1.sortAsc).toEqual(false);
+    expect(sorter1.sortDesc).toEqual(false);
+    expect(sorter2.sortAsc).toEqual(false);
+    expect(sorter2.sortDesc).toEqual(false);
+    expect(sorter3.sortAsc).toEqual(false);
+    expect(sorter3.sortDesc).toEqual(true);
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.ts b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.ts
new file mode 100644
index 0000000..11ada7d
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-table/metron-sorter/metron-sorter.component.ts
@@ -0,0 +1,28 @@
+import { Component, Input } from '@angular/core';
+import {Sort} from '../../../util/enums';
+import {MetronTableDirective, SortEvent} from '../metron-table.directive';
+
+@Component({
+  selector: 'metron-config-sorter',
+  templateUrl: './metron-sorter.component.html',
+  styleUrls: ['./metron-sorter.component.scss']
+})
+export class MetronSorterComponent {
+
+  @Input() sortBy: string;
+
+  sortAsc: boolean = false;
+  sortDesc: boolean = false;
+
+  constructor(private metronTable: MetronTableDirective ) {
+    this.metronTable.onSortColumnChange.subscribe((event: SortEvent) => {
+      this.sortAsc = (event.sortBy === this.sortBy && event.sortOrder === Sort.ASC);
+      this.sortDesc = (event.sortBy === this.sortBy && event.sortOrder === Sort.DSC);
+    });
+  }
+
+  sort() {
+    let order = this.sortAsc ? Sort.DSC : Sort.ASC;
+    this.metronTable.setSort({sortBy: this.sortBy, sortOrder: order});
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-table/metron-table.directive.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-table/metron-table.directive.spec.ts b/metron-interface/metron-config/src/app/shared/metron-table/metron-table.directive.spec.ts
new file mode 100644
index 0000000..b469d46
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-table/metron-table.directive.spec.ts
@@ -0,0 +1,52 @@
+/**
+ * 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.
+ */
+/* tslint:disable:no-unused-variable */
+
+import {Sort} from '../../util/enums';
+import {MetronTableDirective, SortEvent} from './metron-table.directive';
+
+describe('Directive: MetronTable', () => {
+
+  it('should create an instance', () => {
+    let directive = new MetronTableDirective();
+    expect(directive).toBeTruthy();
+  });
+
+  it('should emmit listeners ', () => {
+    let eventToTest: SortEvent = null;
+    let directive = new MetronTableDirective();
+
+    let event1 = { sortBy: 'col1', sortOrder: Sort.ASC };
+    let event2 = { sortBy: 'col2', sortOrder: Sort.DSC };
+
+    directive.onSort.subscribe((sortEvent: SortEvent) => {
+        expect(sortEvent).toEqual(eventToTest);
+    });
+    directive.onSortColumnChange.subscribe((sortEvent: SortEvent) => {
+        expect(sortEvent).toEqual(eventToTest);
+    });
+
+    eventToTest = event1;
+    directive.setSort(eventToTest);
+
+    eventToTest = event2;
+    directive.setSort(eventToTest);
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/metron-table/metron-table.directive.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/metron-table/metron-table.directive.ts b/metron-interface/metron-config/src/app/shared/metron-table/metron-table.directive.ts
new file mode 100644
index 0000000..682fecc
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/metron-table/metron-table.directive.ts
@@ -0,0 +1,42 @@
+/**
+ * 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.
+ */
+/* tslint:disable:directive-selector-name */
+import {Directive, Output, EventEmitter} from '@angular/core';
+import {Sort} from '../../util/enums';
+
+export interface SortEvent {
+  sortBy: string;
+  sortOrder: Sort;
+}
+
+@Directive({
+  selector: '[metron-config-table]'
+})
+
+export class MetronTableDirective {
+
+  @Output() onSort = new EventEmitter<SortEvent>();
+
+  onSortColumnChange = new EventEmitter<SortEvent>();
+
+  public setSort(sortEvent: SortEvent): void {
+    this.onSortColumnChange.emit(sortEvent);
+    this.onSort.emit(sortEvent);
+  }
+
+}

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

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/multiple-input/index.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/multiple-input/index.ts b/metron-interface/metron-config/src/app/shared/multiple-input/index.ts
new file mode 100644
index 0000000..3697be3
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/multiple-input/index.ts
@@ -0,0 +1 @@
+export * from './multiple-input.component';

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.html b/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.html
new file mode 100644
index 0000000..f070dd7
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.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.
+  -->
+<!--Configured-->
+<div *ngFor="let configuredItem of configuredItems; let i = index" class="row mx-0">
+  <div class="col-md-10 px-0">
+    <input *ngIf="type==='text'" class="form-control disabled" readonly [ngModel]="configuredItem.name">
+    <select *ngIf="type==='select'" #select class="form-control" [ngModel]="configuredItem.name" (change)="onUpdate(configuredItem,select.value)">
+      <option *ngFor="let availableFunction of getAvailableFunctions()" [disabled]="availableFunction===''" [selected]="availableFunction===''"> {{ availableFunction.name }} </option>
+    </select>
+  </div>
+  <div class="col-md-2 px-0">
+    <i class="fa fa-minus fa-border icon-button" aria-hidden="true" style="margin-left: 0.3rem;" (click)="onRemove(configuredItem)"></i>
+  </div>
+</div>
+
+<!--new-->
+<div class="row mx-0" *ngIf="(this.configuredItems.length !== this.availableItems.length) || configuredItems.length === 0 ">
+  <div class="col-md-10 px-0">
+    <select #select class="form-control" (change)="onAdd(select)">
+      <option [disabled]="true" [selected]="true"></option>
+      <option *ngFor="let availableFunction of getAvailableFunctions()"> {{ availableFunction.name }} </option>
+    </select>
+  </div>
+  <div class="col-md-3 px-0"> </div>
+</div>
+

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.scss b/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.scss
new file mode 100644
index 0000000..611a44a
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.scss
@@ -0,0 +1,21 @@
+/**
+ * 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-control
+{
+  margin-bottom: 4px;
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.spec.ts b/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.spec.ts
new file mode 100644
index 0000000..0bf9b76
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.spec.ts
@@ -0,0 +1,147 @@
+/**
+ * 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.
+ */
+/* tslint:disable:no-unused-variable */
+
+import {async, TestBed, ComponentFixture} from '@angular/core/testing';
+import { MultipleInputComponent } from './multiple-input.component';
+import {SharedModule} from '../shared.module';
+import {AutocompleteOption} from '../../model/autocomplete-option';
+
+describe('Component: MultipleInput', () => {
+
+  let fixture: ComponentFixture<MultipleInputComponent>;
+  let component: MultipleInputComponent;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [SharedModule],
+      declarations: [ MultipleInputComponent ],
+      providers: [
+        MultipleInputComponent
+      ]
+    });
+
+    fixture = TestBed.createComponent(MultipleInputComponent);
+    component = fixture.componentInstance;
+
+  }));
+
+  it('should create an instance', () => {
+    expect(component).toBeDefined();
+  });
+
+  it('should get AvailableFunctions', () => {
+    let availableItems: AutocompleteOption[] = [];
+    availableItems.push(new AutocompleteOption('option1'));
+    availableItems.push(new AutocompleteOption('option2'));
+    availableItems.push(new AutocompleteOption('option3'));
+    availableItems.push(new AutocompleteOption('option4'));
+
+    let configuredItems: AutocompleteOption[] = [];
+    configuredItems.push(new AutocompleteOption('option1'));
+    configuredItems.push(new AutocompleteOption('option3'));
+
+    component.allowDuplicates = false;
+
+    component.availableItems = availableItems;
+    expect(component.getAvailableFunctions()).toEqual(availableItems);
+
+    component.configuredItems = configuredItems;
+    expect(component.getAvailableFunctions()).toEqual([new AutocompleteOption('option2'), new AutocompleteOption('option4')]);
+
+    component.allowDuplicates = true;
+    expect(component.getAvailableFunctions()).toEqual(availableItems);
+
+    fixture.destroy();
+  });
+
+  it('should get item by name', () => {
+    let availableItems: AutocompleteOption[] = [];
+    availableItems.push(new AutocompleteOption('option1'));
+    availableItems.push(new AutocompleteOption('option2'));
+    availableItems.push(new AutocompleteOption('option3'));
+
+    component.availableItems = availableItems;
+    expect(component.getAvailableItemByName('option1')).toEqual(new AutocompleteOption('option1'));
+    expect(component.getAvailableItemByName('option4')).toEqual(new AutocompleteOption());
+
+    fixture.destroy();
+  });
+
+  it('should add', () => {
+    spyOn(component.onConfigChange, 'emit');
+
+    let availableItems: AutocompleteOption[] = [];
+    availableItems.push(new AutocompleteOption('option1'));
+    availableItems.push(new AutocompleteOption('option2'));
+    availableItems.push(new AutocompleteOption('option3'));
+
+    let select = {'value': 'option1'};
+    component.availableItems = availableItems;
+    component.onAdd(select);
+    expect(component.configuredItems.length).toEqual(1);
+    expect(component.configuredItems[0].name).toEqual('option1');
+    expect(component.showAddNew).toEqual(false);
+    expect(component.onConfigChange.emit).toHaveBeenCalled();
+    expect(select.value).toEqual('');
+
+    fixture.destroy();
+  });
+
+  it('should remove', () => {
+    spyOn(component.onConfigChange, 'emit');
+
+    let configuredItems: AutocompleteOption[] = [];
+    configuredItems.push(new AutocompleteOption('option1'));
+    configuredItems.push(new AutocompleteOption('option2'));
+    configuredItems.push(new AutocompleteOption('option3'));
+
+    component.configuredItems = configuredItems;
+    component.onRemove(new AutocompleteOption('option1'));
+    expect(component.configuredItems.length).toEqual(2);
+    expect(component.onConfigChange.emit).toHaveBeenCalled();
+
+    fixture.destroy();
+  });
+
+  it('should update', () => {
+    spyOn(component.onConfigChange, 'emit');
+
+    let availableItems: AutocompleteOption[] = [];
+    availableItems.push(new AutocompleteOption('option1'));
+    availableItems.push(new AutocompleteOption('option2'));
+    availableItems.push(new AutocompleteOption('option3'));
+    availableItems.push(new AutocompleteOption('option4'));
+
+    let configuredItems: AutocompleteOption[] = [];
+    let option1 = new AutocompleteOption('option1');
+    configuredItems.push(option1);
+    configuredItems.push(new AutocompleteOption('option3'));
+
+    component.availableItems = availableItems;
+    component.configuredItems = configuredItems;
+
+    component.onUpdate(option1, 'option4');
+    expect(component.configuredItems.length).toEqual(2);
+    expect(component.configuredItems[0].name).toEqual('option4');
+    expect(component.onConfigChange.emit).toHaveBeenCalled();
+
+    fixture.destroy();
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.ts b/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.ts
new file mode 100644
index 0000000..eb76f40
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/multiple-input/multiple-input.component.ts
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import {AutocompleteOption} from '../../model/autocomplete-option';
+
+@Component({
+  selector: 'metron-config-multiple-input',
+  templateUrl: './multiple-input.component.html',
+  styleUrls: ['./multiple-input.component.scss']
+})
+export class MultipleInputComponent {
+
+  @Input() type: string = 'text';
+  @Input() allowDuplicates: boolean = true;
+  @Input() configuredItems: AutocompleteOption[] = [];
+  @Input() availableItems: AutocompleteOption[] = [];
+
+  @Output() onConfigChange: EventEmitter<void> = new EventEmitter<void>();
+
+  showAddNew: boolean = false;
+
+  constructor() { }
+
+  getAvailableFunctions(): AutocompleteOption[] {
+    if (this.allowDuplicates) {
+      return this.availableItems;
+    }
+
+    let tAvailable: AutocompleteOption[] = [];
+    let configuredFunctionNames: string[] = [];
+    for (let item of this.configuredItems) {
+      configuredFunctionNames.push(item.name);
+    }
+    for (let item of this.availableItems) {
+      if (configuredFunctionNames.indexOf(item.name) === -1) {
+        tAvailable.push(item);
+      }
+    }
+
+    return tAvailable;
+  }
+
+  getAvailableItemByName(name: string): AutocompleteOption {
+    for (let item of this.availableItems) {
+      if (name === item.name) {
+        return item;
+      }
+    }
+
+    return new AutocompleteOption();
+  }
+
+  onAdd(select: any) {
+    this.configuredItems.push(this.getAvailableItemByName(select.value));
+    select.value = '';
+    this.onConfigChange.emit();
+  }
+
+  onRemove(configuredItem: AutocompleteOption) {
+    this.configuredItems.splice(this.configuredItems.indexOf(configuredItem), 1);
+    this.onConfigChange.emit();
+  }
+
+  onUpdate(configuredItem: AutocompleteOption, value: string) {
+    this.configuredItems.splice(this.configuredItems.indexOf(configuredItem), 1, this.getAvailableItemByName(value));
+    this.onConfigChange.emit();
+  }
+}

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

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/number-spinner/index.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/number-spinner/index.ts b/metron-interface/metron-config/src/app/shared/number-spinner/index.ts
new file mode 100644
index 0000000..b37c764
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/number-spinner/index.ts
@@ -0,0 +1 @@
+export * from './number-spinner.component';

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.html b/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.html
new file mode 100644
index 0000000..b63908f
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.html
@@ -0,0 +1,22 @@
+<!--
+  Licensed to the Apache Software
+	Foundation (ASF) under one or more contributor license agreements. See the
+	NOTICE file distributed with this work for additional information regarding
+	copyright ownership. The ASF licenses this file to You under the Apache License,
+	Version 2.0 (the "License"); you may not use this file except in compliance
+	with the License. You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software distributed
+	under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+	OR CONDITIONS OF ANY KIND, either express or implied. See the License for
+  the specific language governing permissions and limitations under the License.
+  -->
+<div class="input-group spinner">
+  <input type="text" class="form-control" [(ngModel)]="value">
+  <div class="input-group-btn-vertical">
+    <button class="btn btn-default" type="button" (click)="value=value+1" [disabled]="value >= max"><i class="fa fa-caret-up"></i></button>
+    <button class="btn btn-default" type="button" (click)="value=value-1" [disabled]="value <= min"><i class="fa fa-caret-down"></i></button>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.scss b/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.scss
new file mode 100644
index 0000000..b4352c4
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.scss
@@ -0,0 +1,55 @@
+/**
+ * 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.
+ */
+.input-group-btn-vertical
+{
+  width: 2%;
+  position: relative;
+  white-space: nowrap;
+  vertical-align: middle;
+  display: table-cell;
+}
+
+.input-group-btn-vertical > .btn
+{
+  display: block;
+  float: none;
+  width: 100%;
+  max-width: 100%;
+  padding: 8px;
+  margin-left: -1px;
+  position: relative;
+  border-radius: 0;
+}
+
+.input-group-btn-vertical > .btn:first-child
+{
+  border-top-right-radius: 0.25em;
+}
+
+.input-group-btn-vertical > .btn:last-child
+{
+  margin-top: -1px;
+  border-bottom-right-radius: 0.25em;
+}
+
+.input-group-btn-vertical i
+{
+  position: absolute;
+  top: 0;
+  left: 4px;
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.spec.ts b/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.spec.ts
new file mode 100644
index 0000000..ca3042d
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.spec.ts
@@ -0,0 +1,46 @@
+/**
+ * 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.
+ */
+/* tslint:disable:no-unused-variable */
+
+import {NumberSpinnerComponent} from './number-spinner.component';
+
+describe('NumberSpinnerComponent', () => {
+
+  it('should create an instance', () => {
+    expect(new NumberSpinnerComponent()).toBeTruthy();
+  });
+
+  it('spec for all inherited functions', () => {
+    let numberSpinnerComponent = new NumberSpinnerComponent();
+
+    numberSpinnerComponent.writeValue(10);
+    expect(numberSpinnerComponent.innerValue).toEqual(10);
+    expect(numberSpinnerComponent.value).toEqual(10);
+
+    numberSpinnerComponent.registerOnChange((_: any) => {});
+    numberSpinnerComponent.value = 11;
+    expect(numberSpinnerComponent.innerValue).toEqual(11);
+    expect(numberSpinnerComponent.value).toEqual(11);
+
+    numberSpinnerComponent.value = 'abc';
+    expect(numberSpinnerComponent.innerValue).toEqual(11);
+    expect(numberSpinnerComponent.value).toEqual(11);
+
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.ts b/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.ts
new file mode 100644
index 0000000..1fd1521
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/number-spinner/number-spinner.component.ts
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Component, Input, forwardRef } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
+
+@Component({
+  selector: 'metron-config-number-spinner',
+  templateUrl: './number-spinner.component.html',
+  styleUrls: ['./number-spinner.component.scss'],
+  providers: [
+    {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => NumberSpinnerComponent),
+      multi: true
+    }
+  ]
+})
+export class NumberSpinnerComponent implements ControlValueAccessor {
+
+  @Input() min: number;
+  @Input() max: number;
+
+  innerValue: number = 0;
+
+  private onTouchedCallback ;
+  private onChangeCallback ;
+
+  writeValue(val: any) {
+    this.innerValue = val;
+  }
+
+  registerOnChange(fn: any) {
+    this.onChangeCallback = fn;
+  }
+
+  registerOnTouched(fn: any) {
+    this.onTouchedCallback = fn;
+  }
+
+  get value(): any {
+    if (typeof this.innerValue === 'string') {
+      this.innerValue = parseInt(this.innerValue, 10);
+    }
+    return this.innerValue;
+  };
+
+  set value(v: any) {
+    v = Number(v);
+    if (!isNaN(v) && v !== this.innerValue) {
+      this.innerValue = v;
+      this.onChangeCallback(v);
+    }
+  }
+
+}

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

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.html b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.html
new file mode 100644
index 0000000..887211c
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.html
@@ -0,0 +1,22 @@
+<!--
+  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.
+  -->
+<label attr.for="sampleData">SAMPLE&nbsp;( {{sampleDataIndex + 1}} of {{sampleData.length}} )</label>
+<div>
+  <i class="fa fa-caret-left sample-iterator sample-unavailable" aria-hidden="true" [class.sample-available]="sampleDataIndex > 0" [class.sample-unavailable]="sampleDataIndex < 1" (click)="getPreviousSample()"></i>
+  <textarea #sampleDataElement type="text" class="form-control sample-input" name="sampleData"  [ngModel]="sampleData[sampleDataIndex]" (blur)="onBlur(sample)"
+            [attr.placeholder]="placeHolderText" (focus)="sampleDataElement.placeholder=''"> </textarea>
+  <i class="fa fa-caret-right sample-iterator sample-available" aria-hidden="true" (click)="getNextSample()"></i>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.scss b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.scss
new file mode 100644
index 0000000..918e468
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.scss
@@ -0,0 +1,66 @@
+/**
+ * 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";
+
+.sample-input {
+  height:60px;
+  display:inline-block;
+  width: 94%;
+  resize: none;
+  margin-right: -4px;
+  margin-left: -4px;
+  border-radius: inherit;
+}
+
+.sample-iterator {
+  background-color:#195d68;
+  vertical-align: top;
+  width: 3%;
+  height: 60px;
+  padding-top: 22px;
+  padding-left: 6px;
+  cursor: pointer;
+}
+
+.sample-available {
+  color: #27aae1;
+}
+
+.sample-unavailable {
+  color: #404040;
+}
+
+
+textarea {
+  &::-webkit-input-placeholder {
+    @include place-holder-text;
+  }
+  &:-moz-placeholder {
+    @include place-holder-text;
+  }
+
+  &::-moz-placeholder {
+    @include place-holder-text;
+  }
+
+  &:-ms-input-placeholder {
+    @include place-holder-text;
+  }
+}
+
+

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.spec.ts b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.spec.ts
new file mode 100644
index 0000000..5488209
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.spec.ts
@@ -0,0 +1,188 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {async, TestBed, ComponentFixture} from '@angular/core/testing';
+import {KafkaService} from '../../service/kafka.service';
+import {Observable} from  'rxjs/Observable';
+import {SampleDataComponent} from './sample-data.component';
+import {SharedModule} from '../shared.module';
+import '../../rxjs-operators';
+
+class MockKafkaService {
+  _sample: string[];
+  _sampleCounter: number = 0;
+
+
+  public setSample(sampleMessages: string[]): void {
+    this._sample = sampleMessages;
+    this._sampleCounter = 0;
+  }
+
+  public sample(name: string): Observable<string> {
+
+    if (this._sampleCounter < this._sample.length) {
+      return Observable.create(observer => {
+        observer.next(this._sample[this._sampleCounter++]);
+        observer.complete();
+      });
+    }
+
+    return Observable.throw('Error');
+  }
+}
+
+describe('SampleDataComponent', () => {
+  let fixture: ComponentFixture<SampleDataComponent>;
+  let sampleDataComponent: SampleDataComponent;
+  let kafkaService: MockKafkaService;
+  let sampleMessages: string[] = [
+    'This is first message',
+    'This is second message',
+    'This is third message'
+  ];
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      imports: [SharedModule],
+      declarations: [ SampleDataComponent],
+      providers: [
+        SampleDataComponent,
+        {provide: KafkaService, useClass: MockKafkaService}
+      ]
+    });
+
+    fixture = TestBed.createComponent(SampleDataComponent);
+    sampleDataComponent = fixture.componentInstance;
+    kafkaService = fixture.debugElement.injector.get(KafkaService);
+
+  }));
+
+  it('can instantiate SampleDataComponent', async(() => {
+    expect(sampleDataComponent instanceof SampleDataComponent).toBe(true);
+  }));
+
+
+  it('should emmit messages', async(() => {
+    let expectedMessage;
+    let successCount = 0;
+    let failureCount = 0;
+
+    kafkaService.setSample(sampleMessages);
+
+    sampleDataComponent.onSampleDataChanged.subscribe((message: string) => {
+      ++successCount;
+      expect(message).toEqual(expectedMessage);
+    });
+
+    sampleDataComponent.onSampleDataNotAvailable.subscribe(() => {
+      ++failureCount;
+    });
+
+    spyOn(sampleDataComponent.onSampleDataNotAvailable, 'subscribe');
+    spyOn(sampleDataComponent.onSampleDataChanged, 'subscribe');
+
+    expectedMessage = sampleMessages[0];
+    sampleDataComponent.getNextSample();
+    expect(successCount).toEqual(1);
+    expect(failureCount).toEqual(0);
+
+    expectedMessage = sampleMessages[1];
+    sampleDataComponent.getNextSample();
+    expect(successCount).toEqual(2);
+    expect(failureCount).toEqual(0);
+
+    expectedMessage = sampleMessages[2];
+    sampleDataComponent.getNextSample();
+    expect(successCount).toEqual(3);
+    expect(failureCount).toEqual(0);
+
+    let tmp;
+    expectedMessage = tmp;
+    sampleDataComponent.getNextSample();
+    expect(successCount).toEqual(3);
+    expect(failureCount).toEqual(1);
+
+    expectedMessage = sampleMessages[1];
+    sampleDataComponent.getPreviousSample();
+    expect(successCount).toEqual(4);
+    expect(failureCount).toEqual(1);
+
+    expectedMessage = sampleMessages[0];
+    sampleDataComponent.getPreviousSample();
+    expect(successCount).toEqual(5);
+    expect(failureCount).toEqual(1);
+
+    expectedMessage = sampleMessages[1];
+    sampleDataComponent.getNextSample();
+    expect(successCount).toEqual(6);
+    expect(failureCount).toEqual(1);
+
+    expectedMessage = sampleMessages[0];
+    sampleDataComponent.getPreviousSample();
+    expect(successCount).toEqual(7);
+    expect(failureCount).toEqual(1);
+
+    expectedMessage = tmp;
+    sampleDataComponent.getPreviousSample();
+    expect(successCount).toEqual(7);
+    expect(failureCount).toEqual(1);
+
+  }));
+
+  it('should emmit messages on blur', async(() => {
+
+    let expectedMessage;
+    let successCount = 0;
+
+    kafkaService.setSample(sampleMessages);
+
+    sampleDataComponent.onSampleDataChanged.subscribe((message: string) => {
+      ++successCount;
+      expect(message).toEqual(expectedMessage);
+    });
+
+
+    expectedMessage = 'This is a simple message';
+    fixture.debugElement.nativeElement.querySelector('textarea').value = expectedMessage;
+    sampleDataComponent.onBlur();
+
+    expect(successCount).toEqual(1);
+    expect(sampleDataComponent.sampleDataIndex).toEqual(0);
+    expect(sampleDataComponent.sampleData.length).toEqual(1);
+    expect(sampleDataComponent.sampleData[0]).toEqual(expectedMessage);
+
+
+    expectedMessage = '';
+    fixture.debugElement.nativeElement.querySelector('textarea').value = expectedMessage;
+    sampleDataComponent.onBlur();
+
+    expect(successCount).toEqual(2);
+    expect(sampleDataComponent.sampleDataIndex).toEqual(0);
+    expect(sampleDataComponent.sampleData.length).toEqual(1);
+
+
+    expectedMessage = sampleMessages[0];
+    sampleDataComponent.getNextSample();
+
+    expect(successCount).toEqual(3);
+    expect(sampleDataComponent.sampleDataIndex).toEqual(1);
+    expect(sampleDataComponent.sampleData.length).toEqual(2);
+    expect(sampleDataComponent.sampleData[1]).toEqual(sampleMessages[0]);
+
+  }));
+
+});

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.ts b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.ts
new file mode 100644
index 0000000..801e883
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/sample-data/sample-data.component.ts
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component, Input, Output, EventEmitter, ViewChild, ElementRef} from '@angular/core';
+import {KafkaService} from '../../service/kafka.service';
+
+@Component({
+  selector: 'metron-config-sample-data',
+  templateUrl: 'sample-data.component.html',
+  styleUrls: ['sample-data.component.scss'],
+})
+
+export class SampleDataComponent {
+
+  @Input() topic: string;
+  @Output() onSampleDataChanged = new EventEmitter<string>();
+  @Output() onSampleDataNotAvailable = new EventEmitter<void>();
+
+  @ViewChild('sampleDataElement') sampleDataElement: ElementRef;
+
+  sampleData: string[] = [];
+  sampleDataIndex: number = -1;
+  placeHolderText = 'Paste Sample Message' + '\n' +
+                    'A data sample cannot automatically be loaded. Connect to a Kafka Topic or paste a message here.';
+
+
+  constructor(private kafkaService: KafkaService) {
+  }
+
+
+  getPreviousSample() {
+    if (this.sampleDataIndex > 0) {
+      this.sampleDataIndex = this.sampleDataIndex - 1;
+      this.onSampleDataChanged.emit(this.sampleData[this.sampleDataIndex]);
+    }
+  }
+
+  getNextSample() {
+    if (this.sampleData.length - 1 === this.sampleDataIndex) {
+      this.kafkaService.sample(this.topic).subscribe((sampleData: string) => {
+          this.sampleDataIndex = this.sampleDataIndex + 1;
+          this.sampleData[this.sampleDataIndex] = sampleData;
+          this.onSampleDataChanged.emit(this.sampleData[this.sampleDataIndex]);
+        },
+        error => {
+          this.onSampleDataNotAvailable.emit();
+        });
+    } else {
+      this.sampleDataIndex = this.sampleDataIndex + 1;
+      this.onSampleDataChanged.emit(this.sampleData[this.sampleDataIndex]);
+    }
+
+  }
+
+  onBlur() {
+    let currentValue = this.sampleDataElement.nativeElement.value;
+
+    if (currentValue.trim() !== '' && this.sampleData[this.sampleDataIndex] !== currentValue) {
+      this.sampleDataIndex = this.sampleDataIndex + 1;
+      this.sampleData[this.sampleDataIndex] = currentValue;
+      this.onSampleDataChanged.emit(this.sampleData[this.sampleDataIndex]);
+    }
+
+    if (currentValue.trim() === '') {
+      this.sampleDataElement.nativeElement.placeholder = this.placeHolderText;
+      this.onSampleDataChanged.emit('');
+    }
+  }
+
+}

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

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/shared/shared.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/shared/shared.module.ts b/metron-interface/metron-config/src/app/shared/shared.module.ts
new file mode 100644
index 0000000..4b2c4cc
--- /dev/null
+++ b/metron-interface/metron-config/src/app/shared/shared.module.ts
@@ -0,0 +1,29 @@
+/**
+ * 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 { CommonModule }        from '@angular/common';
+import { FormsModule }         from '@angular/forms';
+import {MetronModalComponent} from './metron-modal/metron-modal.component';
+
+@NgModule({
+  imports:      [ CommonModule ],
+  declarations: [ MetronModalComponent ],
+  exports:      [ MetronModalComponent,
+    CommonModule, FormsModule ]
+})
+export class SharedModule { }

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/util/enums.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/util/enums.ts b/metron-interface/metron-config/src/app/util/enums.ts
new file mode 100644
index 0000000..b89af94
--- /dev/null
+++ b/metron-interface/metron-config/src/app/util/enums.ts
@@ -0,0 +1,21 @@
+/**
+ * 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 enum Sort {
+    ASC,
+    DSC
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/util/httpUtil.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/util/httpUtil.ts b/metron-interface/metron-config/src/app/util/httpUtil.ts
new file mode 100644
index 0000000..a93e66e
--- /dev/null
+++ b/metron-interface/metron-config/src/app/util/httpUtil.ts
@@ -0,0 +1,45 @@
+/**
+ * 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 {Response} from '@angular/http';
+import {Observable}     from 'rxjs/Observable';
+import {RestError} from '../model/rest-error';
+export class HttpUtil {
+
+  public static extractString(res: Response): string {
+    let text: string = res.text();
+    return text || '';
+  }
+
+  public static extractData(res: Response): any {
+    let body = res.json();
+    return body || {};
+  }
+
+  public static handleError(res: Response): Observable<RestError> {
+    // In a real world app, we might use a remote logging infrastructure
+    // We'd also dig deeper into the error to get a better message
+    let restError: RestError;
+    if (res.status !== 404) {
+      restError = res.json();
+    } else {
+      restError = new RestError();
+      restError.responseCode = 404;
+    }
+    return Observable.throw(restError);
+  }
+}



Mime
View raw message