You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by rm...@apache.org on 2017/04/11 13:51:16 UTC
[05/12] incubator-metron git commit: METRON-623 Management UI
[contributed by Raghu Mitra Kandikonda and Ryan Merriman] closes
apache/incubator-metron#489
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html
new file mode 100644
index 0000000..9d67df6
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.html
@@ -0,0 +1,88 @@
+<!--
+ Licensed to the Apache Software
+ Foundation (ASF) under one or more contributor license agreements. See the
+ NOTICE file distributed with this work for additional information regarding
+ copyright ownership. The ASF licenses this file to You under the Apache License,
+ Version 2.0 (the "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software distributed
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
+ OR CONDITIONS OF ANY KIND, either express or implied. See the License for
+ the specific language governing permissions and limitations under the License.
+ -->
+<metron-config-sensor-rule-editor *ngIf="showTextEditor" [riskLevelRule]="currentRiskLevelRule"
+ (onCancelTextEditor)="onCancelTextEditor()"
+ (onSubmitTextEditor)="onSubmitTextEditor($event)"></metron-config-sensor-rule-editor>
+
+<div class="metron-slider-pane-edit fill load-left-to-right dialog1x">
+
+ <div class="form-title">Threat Triage Rules</div>
+ <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="onClose()"></i>
+
+
+ <form role="form" class="threat-intel-form">
+ <div class="form-group threat-triage-aggregator">
+ <label attr.for="aggregator">AGGREGATOR</label>
+ <select class="form-control" name="aggregator"
+ [(ngModel)]="sensorEnrichmentConfig.threatIntel.triageConfig.aggregator">
+ <option *ngFor="let aggregator of availableAggregators" [value]="aggregator">{{aggregator}}</option>
+ </select>
+ </div>
+ <div class="threat-triage-summary">
+ <div class="form-group">
+ <div class="rules-summary-title">Rules</div>
+ <div class="row mx-0">
+ <div class="btn" (click)="onFilterChange(threatTriageFilter.HIGH)" [ngClass]="{'filter-button': filter != threatTriageFilter.HIGH, 'filter-button-selected': filter == threatTriageFilter.HIGH}">
+ <i aria-hidden="true" class="fa fa-circle" style="color: red"></i> {{highAlerts}}
+ </div>
+ <div class="btn" (click)="onFilterChange(threatTriageFilter.MEDIUM)" [ngClass]="{'filter-button': filter != threatTriageFilter.MEDIUM, 'filter-button-selected': filter == threatTriageFilter.MEDIUM}">
+ <i aria-hidden="true" class="fa fa-circle" style="color: orange"></i> {{mediumAlerts}}
+ </div>
+ <div class="btn" (click)="onFilterChange(threatTriageFilter.LOW)" [ngClass]="{'filter-button': filter != threatTriageFilter.LOW, 'filter-button-selected': filter == threatTriageFilter.LOW}">
+ <i aria-hidden="true" class="fa fa-circle" style="color: khaki"></i> {{lowAlerts}}
+ </div>
+ </div>
+ <div class="row mx-0 threat-triage-rules-sort">
+ <span class="label">Sort by </span>
+ <li class="nav-item dropdown">
+ <span class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">{{sortOrderOption[sortOrder].replace('_', ' ')}}</span>
+ <div class="dropdown-menu bg-inverse">
+ <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Highest_Score)">Highest Score</span>
+ <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Lowest_Score)">Lowest Score</span>
+ <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Highest_Name)">Highest Name</span>
+ <span class="dropdown-item" (click)="onSortOrderChange(sortOrderOption.Lowest_Name)">Lowest Name</span>
+ </div>
+ </li>
+ </div>
+ </div>
+ </div>
+ <div class="form-group threat-triage-rules-list">
+ <div *ngFor="let riskLevelRule of this.visibleRules">
+ <div class="row mx-0 py-0">
+ <div class="threat-triage-rule-row" style="color: khaki; font-size: 30px; margin-top: -7px" [style.color]="getRuleColor(riskLevelRule)">
+ <b>I</b>
+ </div>
+ <div class="threat-triage-rule-row" style="font-size: small; width: 8%">
+ {{ riskLevelRule.score }}
+ </div>
+ <div class="threat-triage-rule-row threat-triage-rule-str">
+ {{ getDisplayName(riskLevelRule) }}
+ </div>
+ <div class="threat-triage-rule-row" style=""><i class="fa fa-i-cursor" aria-hidden="true"
+ style="cursor: pointer;" (click)="onEditRule(riskLevelRule)"></i></div>
+
+ <div class="threat-triage-rule-row" style=""><i class="fa fa-trash-o" aria-hidden="true" (click)="onDeleteRule(riskLevelRule)" style="cursor: pointer"></i></div>
+ </div>
+ <div class="form-seperator-edit"></div>
+ </div>
+ </div>
+ <div class="form-group mx-0 py-0">
+ <button class="btn form-enable-disable-button add-button" (click)="onNewRule()"><i
+ aria-hidden="true" class="fa fa-plus fa-4"></i></button>
+ </div>
+ </form>
+
+</div>
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss
new file mode 100644
index 0000000..bbc17a0
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.scss
@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@import "../../_variables.scss";
+@import "../../../styles.scss";
+
+textarea
+{
+ height: auto;
+}
+
+.form-title
+{
+ padding-left: 25px;
+}
+
+.form-group
+{
+ padding-left: 25px;
+ padding-right: 20px;
+}
+
+.close-button
+{
+ padding-right: 20px;
+}
+
+.threat-triage-summary
+{
+ background-color: $edit-child-highlight;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
+
+.filter-button
+{
+ width: 32%;
+ background: $field-background;
+ border: 1px solid $form-button-border;
+ border-radius: .25em;
+ cursor: default;
+ color: $nav-active-color;
+ i {
+ font-size: smaller;
+ }
+}
+
+.filter-button-selected
+{
+ @extend .filter-button;
+ background-color: #006ea0;
+ color: #bdbdbd;
+}
+
+.threat-triage-aggregator
+{
+ padding-bottom: 10px;
+}
+
+.threat-triage-rules-sort
+{
+ padding-top: 5px;
+ font-size: 12px;
+ font-family: Roboto-Regular;
+ .label
+ {
+ display: inline-block;
+ padding-right: 8px;
+ }
+ .dropdown
+ {
+ list-style: none;
+ display: inline-block;
+ color: $field-button-color;
+ }
+}
+
+.rules-summary-title
+{
+ font-size: 15px;
+}
+
+.threat-triage-rules-list
+{
+ padding-top: 10px;
+}
+
+.threat-triage-rule-row
+{
+ display: inline-block;
+ position: relative;
+ vertical-align: top;
+
+ .fa {
+ color: $nav-active-color;
+ font-size: large;
+ }
+}
+
+.add-button
+{
+ font-size: 20px;
+ width: 100%;
+ padding: 2px;
+
+ i {
+ color: $field-button-color;
+ }
+}
+
+.threat-triage-rule-str
+{
+ width: 64%;
+ padding-right: 10px;
+ font-size: small;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+metron-config-sensor-rule-editor
+{
+ @extend .flexbox-row-reverse;
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts
new file mode 100644
index 0000000..b9e595e
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.spec.ts
@@ -0,0 +1,211 @@
+/**
+ * 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 {SimpleChange, SimpleChanges} from '@angular/core';
+import {Http} from '@angular/http';
+import {async, TestBed, ComponentFixture} from '@angular/core/testing';
+import {SensorThreatTriageComponent, SortOrderOption, ThreatTriageFilter} from './sensor-threat-triage.component';
+import {SensorEnrichmentConfig, ThreatIntelConfig} from '../../model/sensor-enrichment-config';
+import {RiskLevelRule} from '../../model/risk-level-rule';
+import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service';
+import {Observable} from 'rxjs/Observable';
+import '../../rxjs-operators';
+import {SensorThreatTriageModule} from './sensor-threat-triage.module';
+
+class MockSensorEnrichmentConfigService {
+ public getAvailableThreatTriageAggregators(): Observable<string[]> {
+ return Observable.create(observer => {
+ observer.next(['MAX', 'MIN', 'SUM', 'MEAN', 'POSITIVE_MEAN']);
+ observer.complete();
+ });
+ }
+}
+
+describe('Component: SensorThreatTriageComponent', () => {
+
+ let component: SensorThreatTriageComponent;
+ let fixture: ComponentFixture<SensorThreatTriageComponent>;
+ let sensorEnrichmentConfigService: SensorEnrichmentConfigService;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [SensorThreatTriageModule],
+ providers: [
+ {provide: Http},
+ {provide: SensorEnrichmentConfigService, useClass: MockSensorEnrichmentConfigService},
+ ]
+ }).compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(SensorThreatTriageComponent);
+ component = fixture.componentInstance;
+ sensorEnrichmentConfigService = fixture.debugElement.injector.get(SensorEnrichmentConfigService);
+ });
+ }));
+
+ it('should create an instance', () => {
+ expect(component).toBeDefined();
+ fixture.destroy();
+ });
+
+ it('should create an instance', async(() => {
+ spyOn(component, 'init');
+ let changes: SimpleChanges = {'showThreatTriage': new SimpleChange(false, true)};
+
+ component.ngOnChanges(changes);
+ expect(component.init).toHaveBeenCalled();
+
+ changes = {'showStellar': new SimpleChange(true, false)};
+ component.ngOnChanges(changes);
+ expect(component.init['calls'].count()).toEqual(1);
+
+ fixture.destroy();
+ }));
+
+ it('should close panel', async(() => {
+ let numClosed = 0;
+ component.hideThreatTriage.subscribe((closed: boolean) => {
+ numClosed++;
+ });
+
+ component.onClose();
+ expect(numClosed).toEqual(1);
+
+ fixture.destroy();
+ }));
+
+ it('should get color', async(() => {
+ let sensorEnrichmentConfig = new SensorEnrichmentConfig();
+ sensorEnrichmentConfig.threatIntel = Object.assign(new ThreatIntelConfig(), {
+ 'triageConfig': {
+ 'riskLevelRules': {
+ 'ruleA': 15,
+ 'ruleB': 95,
+ 'ruleC': 50
+ },
+ 'aggregator': 'MAX',
+ 'aggregationConfig': {}
+ }
+ });
+ component.sensorEnrichmentConfig = sensorEnrichmentConfig;
+
+ let ruleA = {name: 'ruleA', rule: 'rule A', score: 15, comment: ''};
+ let ruleB = {name: 'ruleB', rule: 'rule B', score: 95, comment: ''};
+ let ruleC = {name: 'ruleC', rule: 'rule C', score: 50, comment: ''};
+
+ expect(component.getRuleColor(ruleA)).toEqual('khaki');
+ expect(component.getRuleColor(ruleB)).toEqual('red');
+ expect(component.getRuleColor(ruleC)).toEqual('orange');
+
+ fixture.destroy();
+ }));
+
+ it('should edit rules', async(() => {
+ let ruleA = {name: 'ruleA', rule: 'rule A', score: 15, comment: ''};
+ let ruleB = {name: 'ruleB', rule: 'rule B', score: 95, comment: ''};
+ let ruleC = {name: 'ruleC', rule: 'rule C', score: 50, comment: ''};
+ let ruleD = {name: 'ruleD', rule: 'rule D', score: 85, comment: ''};
+ let ruleE = {name: 'ruleE', rule: 'rule E', score: 5, comment: ''};
+ let ruleF = {name: 'ruleF', rule: 'rule F', score: 21, comment: ''};
+ let ruleG = {name: 'ruleG', rule: 'rule G', score: 100, comment: ''};
+
+ let sensorEnrichmentConfig = new SensorEnrichmentConfig();
+ sensorEnrichmentConfig.threatIntel = Object.assign(new ThreatIntelConfig(), {
+ 'triageConfig': {
+ 'riskLevelRules': [ruleA, ruleB, ruleC, ruleD, ruleE],
+ 'aggregator': 'MAX',
+ 'aggregationConfig': {}
+ }
+ });
+ component.sensorEnrichmentConfig = sensorEnrichmentConfig;
+
+
+ let changes: SimpleChanges = {'showThreatTriage': new SimpleChange(false, true)};
+ component.ngOnChanges(changes);
+
+ // sorted by score high to low
+ expect(component.visibleRules).toEqual([ruleB, ruleD, ruleC, ruleA, ruleE]);
+ expect(component.lowAlerts).toEqual(2);
+ expect(component.mediumAlerts).toEqual(1);
+ expect(component.highAlerts).toEqual(2);
+
+ // sorted by name high to low
+ component.onSortOrderChange(SortOrderOption.Highest_Name);
+ expect(component.visibleRules).toEqual([ruleE, ruleD, ruleC, ruleB, ruleA]);
+
+ // sorted by score low to high
+ component.onSortOrderChange(SortOrderOption.Lowest_Score);
+ expect(component.visibleRules).toEqual([ruleE, ruleA, ruleC, ruleD, ruleB]);
+
+ // sorted by name low to high
+ component.onSortOrderChange(SortOrderOption.Lowest_Name);
+ expect(component.visibleRules).toEqual([ruleA, ruleB, ruleC, ruleD, ruleE]);
+
+ component.onNewRule();
+ expect(component.currentRiskLevelRule.name).toEqual('');
+ expect(component.currentRiskLevelRule.rule).toEqual('');
+ expect(component.currentRiskLevelRule.score).toEqual(0);
+ expect(component.showTextEditor).toEqual(true);
+
+ component.currentRiskLevelRule = new RiskLevelRule();
+ component.onCancelTextEditor();
+ expect(component.showTextEditor).toEqual(false);
+ expect(component.visibleRules).toEqual([ruleA, ruleB, ruleC, ruleD, ruleE]);
+
+ component.sortOrder = SortOrderOption.Lowest_Score;
+ component.onNewRule();
+ component.currentRiskLevelRule = ruleF;
+ expect(component.showTextEditor).toEqual(true);
+ component.onSubmitTextEditor(ruleF);
+ expect(component.visibleRules).toEqual([ruleE, ruleA, ruleF, ruleC, ruleD, ruleB]);
+ expect(component.lowAlerts).toEqual(2);
+ expect(component.mediumAlerts).toEqual(2);
+ expect(component.highAlerts).toEqual(2);
+ expect(component.showTextEditor).toEqual(false);
+
+ component.onDeleteRule(ruleE);
+ expect(component.visibleRules).toEqual([ruleA, ruleF, ruleC, ruleD, ruleB]);
+ expect(component.lowAlerts).toEqual(1);
+ expect(component.mediumAlerts).toEqual(2);
+ expect(component.highAlerts).toEqual(2);
+
+ component.onFilterChange(ThreatTriageFilter.LOW);
+ expect(component.visibleRules).toEqual([ruleA]);
+
+ component.onFilterChange(ThreatTriageFilter.MEDIUM);
+ expect(component.visibleRules).toEqual([ruleF, ruleC]);
+
+ component.onFilterChange(ThreatTriageFilter.HIGH);
+ expect(component.visibleRules).toEqual([ruleD, ruleB]);
+
+ component.onFilterChange(ThreatTriageFilter.HIGH);
+ expect(component.visibleRules).toEqual([ruleA, ruleF, ruleC, ruleD, ruleB]);
+
+ component.onEditRule(ruleC);
+ expect(component.currentRiskLevelRule).toEqual(ruleC);
+ expect(component.showTextEditor).toEqual(true);
+ component.onSubmitTextEditor(ruleG);
+ expect(component.visibleRules).toEqual([ruleA, ruleF, ruleD, ruleB, ruleG]);
+ expect(component.lowAlerts).toEqual(1);
+ expect(component.mediumAlerts).toEqual(1);
+ expect(component.highAlerts).toEqual(3);
+ expect(component.showTextEditor).toEqual(false);
+
+ fixture.destroy();
+ }));
+
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts
new file mode 100644
index 0000000..db32b04
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.component.ts
@@ -0,0 +1,208 @@
+/**
+ * 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:triple-equals */
+import {Component, Input, EventEmitter, Output, OnChanges, SimpleChanges} from '@angular/core';
+import {SensorEnrichmentConfig } from '../../model/sensor-enrichment-config';
+import {RiskLevelRule} from '../../model/risk-level-rule';
+import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service';
+
+export enum SortOrderOption {
+ Lowest_Score, Highest_Score, Lowest_Name, Highest_Name
+}
+
+export enum ThreatTriageFilter {
+ NONE, LOW, MEDIUM, HIGH
+}
+
+@Component({
+ selector: 'metron-config-sensor-threat-triage',
+ templateUrl: './sensor-threat-triage.component.html',
+ styleUrls: ['./sensor-threat-triage.component.scss']
+})
+
+export class SensorThreatTriageComponent implements OnChanges {
+
+ @Input() showThreatTriage: boolean;
+ @Input() sensorEnrichmentConfig: SensorEnrichmentConfig;
+
+ @Output() hideThreatTriage: EventEmitter<boolean> = new EventEmitter<boolean>();
+ availableAggregators = [];
+ visibleRules: RiskLevelRule[] = [];
+
+ showTextEditor = false;
+ currentRiskLevelRule: RiskLevelRule;
+
+ lowAlerts = 0;
+ mediumAlerts = 0;
+ highAlerts = 0;
+
+ sortOrderOption = SortOrderOption;
+ sortOrder = SortOrderOption.Highest_Score;
+ threatTriageFilter = ThreatTriageFilter;
+ filter: ThreatTriageFilter = ThreatTriageFilter.NONE;
+
+ constructor(private sensorEnrichmentConfigService: SensorEnrichmentConfigService) { }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['showThreatTriage'] && changes['showThreatTriage'].currentValue) {
+ this.init();
+ }
+ }
+
+ init(): void {
+ this.visibleRules = this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules;
+ this.sensorEnrichmentConfigService.getAvailableThreatTriageAggregators().subscribe(results => {
+ this.availableAggregators = results;
+ });
+ this.updateBuckets();
+ this.onSortOrderChange(null);
+ }
+
+ onClose(): void {
+ this.hideThreatTriage.emit(true);
+ }
+
+
+ onSubmitTextEditor(riskLevelRule: RiskLevelRule): void {
+ this.deleteRule(this.currentRiskLevelRule);
+ this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules.push(riskLevelRule);
+ this.showTextEditor = false;
+ this.init();
+ }
+
+ onCancelTextEditor(): void {
+ this.showTextEditor = false;
+ }
+
+ onEditRule(riskLevelRule: RiskLevelRule) {
+ this.currentRiskLevelRule = riskLevelRule;
+ this.showTextEditor = true;
+ }
+
+ onDeleteRule(riskLevelRule: RiskLevelRule) {
+ this.deleteRule(riskLevelRule);
+ this.init();
+ }
+
+ onNewRule(): void {
+ this.currentRiskLevelRule = new RiskLevelRule();
+ this.showTextEditor = true;
+ }
+
+ deleteRule(riskLevelRule: RiskLevelRule) {
+ let index = this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules.indexOf(riskLevelRule);
+ if (index != -1) {
+ this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules.splice(index, 1);
+ }
+ }
+
+ updateBuckets() {
+ this.lowAlerts = 0;
+ this.mediumAlerts = 0;
+ this.highAlerts = 0;
+ for (let riskLevelRule of this.visibleRules) {
+ if (riskLevelRule.score <= 20) {
+ this.lowAlerts++;
+ } else if (riskLevelRule.score >= 80) {
+ this.highAlerts++;
+ } else {
+ this.mediumAlerts++;
+ }
+ }
+ }
+
+ getRuleColor(riskLevelRule: RiskLevelRule): string {
+ let color: string;
+ if (riskLevelRule.score <= 20) {
+ color = 'khaki';
+ } else if (riskLevelRule.score >= 80) {
+ color = 'red';
+ } else {
+ color = 'orange';
+ }
+ return color;
+ }
+
+ onSortOrderChange(sortOrder: any) {
+ if (sortOrder !== null) {
+ this.sortOrder = sortOrder;
+ }
+
+ // all comparisons with enums must be == and not ===
+ if (this.sortOrder == this.sortOrderOption.Highest_Score) {
+ this.visibleRules.sort((a, b) => {
+ return b.score - a.score;
+ });
+ } else if (this.sortOrder == SortOrderOption.Lowest_Score) {
+ this.visibleRules.sort((a, b) => {
+ return a.score - b.score;
+ });
+ } else if (this.sortOrder == SortOrderOption.Lowest_Name) {
+ this.visibleRules.sort((a, b) => {
+ let aName = a.name ? a.name : '';
+ let bName = b.name ? b.name : '';
+ if (aName.toLowerCase() >= bName.toLowerCase()) {
+ return 1;
+ } else if (aName.toLowerCase() < bName.toLowerCase()) {
+ return -1;
+ }
+ });
+ } else {
+ this.visibleRules.sort((a, b) => {
+ let aName = a.name ? a.name : '';
+ let bName = b.name ? b.name : '';
+ if (aName.toLowerCase() >= bName.toLowerCase()) {
+ return -1;
+ } else if (aName.toLowerCase() < bName.toLowerCase()) {
+ return 1;
+ }
+ });
+ }
+ }
+
+ onFilterChange(filter: ThreatTriageFilter) {
+ if (filter === this.filter) {
+ this.filter = ThreatTriageFilter.NONE;
+ } else {
+ this.filter = filter;
+ }
+ this.visibleRules = this.sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules.filter(riskLevelRule => {
+ if (this.filter === ThreatTriageFilter.NONE) {
+ return true;
+ } else {
+ if (this.filter === ThreatTriageFilter.HIGH) {
+ return riskLevelRule.score >= 80;
+ } else if (this.filter === ThreatTriageFilter.LOW) {
+ return riskLevelRule.score <= 20;
+ } else {
+ return riskLevelRule.score < 80 && riskLevelRule.score > 20;
+ }
+ }
+ });
+ this.onSortOrderChange(null);
+ }
+
+ getDisplayName(riskLevelRule: RiskLevelRule): string {
+ if (riskLevelRule.name) {
+ return riskLevelRule.name;
+ } else {
+ return riskLevelRule.rule ? riskLevelRule.rule : '';
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.module.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.module.ts
new file mode 100644
index 0000000..66838d9
--- /dev/null
+++ b/metron-interface/metron-config/src/app/sensors/sensor-threat-triage/sensor-threat-triage.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 {SharedModule} from '../../shared/shared.module';
+import {SensorThreatTriageComponent} from './sensor-threat-triage.component';
+import {SensorRuleEditorModule} from './rule-editor/sensor-rule-editor.module';
+
+
+@NgModule ({
+ imports: [ SharedModule, SensorRuleEditorModule ],
+ declarations: [ SensorThreatTriageComponent ],
+ exports: [ SensorThreatTriageComponent ]
+})
+export class SensorThreatTriageModule {}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/authentication.service.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/authentication.service.spec.ts b/metron-interface/metron-config/src/app/service/authentication.service.spec.ts
new file mode 100644
index 0000000..7f9b296
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/authentication.service.spec.ts
@@ -0,0 +1,190 @@
+/**
+ * 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 {Router} from '@angular/router';
+import {async, inject, TestBed} from '@angular/core/testing';
+import {MockBackend, MockConnection} from '@angular/http/testing';
+import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http';
+import '../rxjs-operators';
+import {Observable} from 'rxjs/Observable';
+import {AuthenticationService} from './authentication.service';
+import {APP_CONFIG, METRON_REST_CONFIG} from '../app.config';
+import {IAppConfig} from '../app.config.interface';
+
+class MockRouter {
+
+ navigateByUrl(url: string) {
+
+ }
+}
+
+describe('AuthenticationService', () => {
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpModule],
+ providers: [
+ AuthenticationService,
+ {provide: XHRBackend, useClass: MockBackend},
+ {provide: Router, useClass: MockRouter},
+ {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+ ]
+ })
+ .compileComponents();
+ }));
+
+ describe('when service functions', () => {
+ it('can instantiate service when inject service',
+ inject([AuthenticationService], (service: AuthenticationService) => {
+ expect(service instanceof AuthenticationService).toBe(true);
+ }));
+
+ });
+
+ describe('when service functions', () => {
+ let authenticationService: AuthenticationService;
+ let mockBackend: MockBackend;
+ let userResponse: Response;
+ let userName = 'test';
+ let router: MockRouter;
+
+ beforeEach(inject([Http, XHRBackend, Router, AuthenticationService, APP_CONFIG],
+ (http: Http, be: MockBackend, mRouter: MockRouter, service: AuthenticationService, config: IAppConfig) => {
+ mockBackend = be;
+ router = mRouter;
+ authenticationService = service;
+ userResponse = new Response(new ResponseOptions({status: 200, body: userName}));
+ }));
+
+ it('init', async(inject([], () => {
+ let userResponsesuccess = true;
+ spyOn(authenticationService.onLoginEvent, 'emit');
+ spyOn(authenticationService, 'getCurrentUser').and.callFake(function() {
+ if (userResponsesuccess) {
+ return Observable.create(observer => {
+ observer.next(userResponse);
+ observer.complete();
+ });
+ }
+
+ return Observable.throw('Error');
+ });
+
+ authenticationService.init();
+ expect(authenticationService.onLoginEvent.emit).toHaveBeenCalledWith(true);
+
+ userResponsesuccess = false;
+ authenticationService.init();
+ expect(authenticationService.onLoginEvent.emit['calls'].count()).toEqual(2);
+
+ })));
+
+ it('login', async(inject([], () => {
+ let responseMessageSuccess = true;
+ mockBackend.connections.subscribe((c: MockConnection) => {
+ if (responseMessageSuccess) {
+ c.mockRespond(userResponse);
+ } else {
+ c.mockError(new Error('login failed'));
+ }
+ });
+
+ spyOn(router, 'navigateByUrl');
+ spyOn(authenticationService.onLoginEvent, 'emit');
+ authenticationService.login('test', 'test', error => {
+ });
+
+ expect(router.navigateByUrl).toHaveBeenCalledWith('/sensors');
+ expect(authenticationService.onLoginEvent.emit).toHaveBeenCalled();
+
+ responseMessageSuccess = false;
+ let errorSpy = jasmine.createSpy('error');
+ authenticationService.login('test', 'test', errorSpy);
+ expect(errorSpy).toHaveBeenCalledWith(new Error('login failed'));
+
+ })));
+
+ it('logout', async(inject([], () => {
+ let responseMessageSuccess = true;
+ mockBackend.connections.subscribe((c: MockConnection) => {
+ if (responseMessageSuccess) {
+ c.mockRespond(userResponse);
+ } else {
+ c.mockError(new Error('login failed'));
+ }
+ });
+
+ spyOn(router, 'navigateByUrl');
+ spyOn(authenticationService.onLoginEvent, 'emit');
+ authenticationService.logout();
+
+ expect(router.navigateByUrl).toHaveBeenCalledWith('/login');
+ expect(authenticationService.onLoginEvent.emit).toHaveBeenCalled();
+
+ responseMessageSuccess = false;
+ spyOn(console, 'log');
+ authenticationService.logout();
+ expect(console.log).toHaveBeenCalled();
+
+ })));
+
+ it('checkAuthentication', async(inject([], () => {
+ let isAuthenticated = false;
+ spyOn(router, 'navigateByUrl');
+ spyOn(authenticationService, 'isAuthenticated').and.callFake(function() {
+ return isAuthenticated;
+ });
+
+ authenticationService.checkAuthentication();
+ expect(router.navigateByUrl).toHaveBeenCalledWith('/login');
+
+ isAuthenticated = true;
+ authenticationService.checkAuthentication();
+ expect(router.navigateByUrl['calls'].count()).toEqual(1);
+ })));
+
+ it('getCurrentUser', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => userResponse);
+ authenticationService.getCurrentUser(null).subscribe(
+ result => {
+ expect(result).toEqual('');
+ }, error => console.log(error));
+ })));
+
+ it('isAuthenticationChecked', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(userResponse));
+
+ expect(authenticationService.isAuthenticationChecked()).toEqual(false);
+
+ authenticationService.login('test', 'test', null);
+ expect(authenticationService.isAuthenticationChecked()).toEqual(true);
+
+ })));
+
+ it('isAuthenticated', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(userResponse));
+
+ expect(authenticationService.isAuthenticated()).toEqual(false);
+
+ authenticationService.login('test', 'test', null);
+ expect(authenticationService.isAuthenticated()).toEqual(true);
+
+ })));
+ });
+
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/authentication.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/authentication.service.ts b/metron-interface/metron-config/src/app/service/authentication.service.ts
new file mode 100644
index 0000000..5fd50f3
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/authentication.service.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 {Injectable, EventEmitter, Inject} from '@angular/core';
+import {Http, Headers, RequestOptions, Response} from '@angular/http';
+import {Router} from '@angular/router';
+import {Observable} from 'rxjs/Observable';
+import {IAppConfig} from '../app.config.interface';
+import {APP_CONFIG} from '../app.config';
+
+@Injectable()
+export class AuthenticationService {
+
+ private static USER_NOT_VERIFIED: string = 'USER-NOT-VERIFIED';
+ private currentUser: string = AuthenticationService.USER_NOT_VERIFIED;
+ loginUrl: string = this.config.apiEndpoint + '/user';
+ logoutUrl: string = '/logout';
+ defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'};
+ onLoginEvent: EventEmitter<boolean> = new EventEmitter<boolean>();
+
+ constructor(private http: Http, private router: Router, @Inject(APP_CONFIG) private config: IAppConfig) {
+ this.init();
+ }
+
+ public init() {
+ this.getCurrentUser(new RequestOptions({headers: new Headers(this.defaultHeaders)})).subscribe((response: Response) => {
+ this.currentUser = response.text();
+ if (this.currentUser) {
+ this.onLoginEvent.emit(true);
+ }
+ }, error => {
+ this.onLoginEvent.emit(false);
+ });
+ }
+
+ public login(username: string, password: string, onError): void {
+ let loginHeaders: Headers = new Headers(this.defaultHeaders);
+ loginHeaders.append('authorization', 'Basic ' + btoa(username + ':' + password));
+ let loginOptions: RequestOptions = new RequestOptions({headers: loginHeaders});
+ this.getCurrentUser(loginOptions).subscribe((response: Response) => {
+ this.currentUser = response.text();
+ this.router.navigateByUrl('/sensors');
+ this.onLoginEvent.emit(true);
+ },
+ error => {
+ onError(error);
+ });
+ }
+
+ public logout(): void {
+ this.http.post(this.logoutUrl, {}, new RequestOptions({headers: new Headers(this.defaultHeaders)})).subscribe(response => {
+ this.currentUser = AuthenticationService.USER_NOT_VERIFIED;
+ this.onLoginEvent.emit(false);
+ this.router.navigateByUrl('/login');
+ },
+ error => {
+ console.log(error);
+ });
+ }
+
+ public checkAuthentication() {
+ if (!this.isAuthenticated()) {
+ this.router.navigateByUrl('/login');
+ }
+ }
+
+ public getCurrentUser(options: RequestOptions): Observable<Response> {
+ return this.http.get(this.loginUrl, options);
+ }
+
+ public isAuthenticationChecked(): boolean {
+ return this.currentUser !== AuthenticationService.USER_NOT_VERIFIED;
+ }
+
+ public isAuthenticated(): boolean {
+ return this.currentUser !== AuthenticationService.USER_NOT_VERIFIED && this.currentUser != null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/global-config.service.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/global-config.service.spec.ts b/metron-interface/metron-config/src/app/service/global-config.service.spec.ts
new file mode 100644
index 0000000..f53c3f3
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/global-config.service.spec.ts
@@ -0,0 +1,99 @@
+/**
+ * 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 {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 {GlobalConfigService} from './global-config.service';
+
+describe('GlobalConfigService', () => {
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpModule],
+ providers: [
+ GlobalConfigService,
+ {provide: XHRBackend, useClass: MockBackend},
+ {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+ ]
+ })
+ .compileComponents();
+ }));
+
+ it('can instantiate service when inject service',
+ inject([GlobalConfigService], (service: GlobalConfigService) => {
+ expect(service instanceof GlobalConfigService).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 GlobalConfigService(http, config);
+ expect(service instanceof GlobalConfigService).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 globalConfigService: GlobalConfigService;
+ let mockBackend: MockBackend;
+ let globalConfig = {'field': 'value'};
+ let globalConfigResponse: Response;
+ let deleteResponse: Response;
+
+ beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => {
+ mockBackend = be;
+ globalConfigService = new GlobalConfigService(http, config);
+ globalConfigResponse = new Response(new ResponseOptions({status: 200, body: globalConfig}));
+ }));
+
+ it('post', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(globalConfigResponse));
+
+ globalConfigService.post(globalConfig).subscribe(
+ result => {
+ expect(result).toEqual(globalConfig);
+ }, error => console.log(error));
+ })));
+
+ it('get', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(globalConfigResponse));
+
+ globalConfigService.get().subscribe(
+ result => {
+ expect(result).toEqual(globalConfig);
+ }, error => console.log(error));
+ })));
+
+ it('deleteSensorParserConfigs', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deleteResponse));
+
+ globalConfigService.delete().subscribe(result => {
+ expect(result.status).toEqual(200);
+ });
+ })));
+ });
+
+});
+
+
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/global-config.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/global-config.service.ts b/metron-interface/metron-config/src/app/service/global-config.service.ts
new file mode 100644
index 0000000..1ed4325
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/global-config.service.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 {Injectable, Inject} from '@angular/core';
+import {Http, Headers, RequestOptions, Response, ResponseOptions} from '@angular/http';
+import {Observable} from 'rxjs/Observable';
+import {HttpUtil} from '../util/httpUtil';
+import {IAppConfig} from '../app.config.interface';
+import {APP_CONFIG} from '../app.config';
+
+@Injectable()
+export class GlobalConfigService {
+ url = this.config.apiEndpoint + '/global/config';
+ defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'};
+
+ private globalConfig = {
+
+ };
+
+ constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) {
+ this.globalConfig['solr.collection'] = 'metron';
+ this.globalConfig['storm.indexingWorkers'] = 1;
+ this.globalConfig['storm.indexingExecutors'] = 2;
+ this.globalConfig['hdfs.boltBatchSize'] = 5000;
+ this.globalConfig['hdfs.boltFieldDelimiter'] = '|';
+ this.globalConfig['hdfs.boltFileRotationSize'] = 5;
+ this.globalConfig['hdfs.boltCompressionCodecClass'] = 'org.apache.hadoop.io.compress.SnappyCodec';
+ this.globalConfig['hdfs.indexOutput'] = '/tmp/metron/enriched';
+ this.globalConfig['kafkaWriter.topic'] = 'outputTopic';
+ this.globalConfig['kafkaWriter.keySerializer'] = 'org.apache.kafka.common.serialization.StringSerializer';
+ this.globalConfig['kafkaWriter.valueSerializer'] = 'org.apache.kafka.common.serialization.StringSerializer';
+ this.globalConfig['kafkaWriter.requestRequiredAcks'] = 1;
+ this.globalConfig['solrWriter.indexName'] = 'alfaalfa';
+ this.globalConfig['solrWriter.shards'] = 1;
+ this.globalConfig['solrWriter.replicationFactor'] = 1;
+ this.globalConfig['solrWriter.batchSize'] = 50;
+ }
+
+ public post(globalConfig: {}): Observable<{}> {
+ return this.http.post(this.url, globalConfig, new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public get(): Observable<{}> {
+ return this.http.get(this.url , new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public delete(): Observable<Response> {
+ let responseOptions = new ResponseOptions();
+ responseOptions.status = 200;
+ let response = new Response(responseOptions);
+ return Observable.create(observer => {
+ observer.next(response);
+ observer.complete();
+ });
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts b/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts
new file mode 100644
index 0000000..da45a80
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/grok-validation.service.spec.ts
@@ -0,0 +1,106 @@
+/**
+ * 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 {GrokValidationService} from './grok-validation.service';
+import {GrokValidation} from '../model/grok-validation';
+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('GrokValidationService', () => {
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpModule],
+ providers: [
+ GrokValidationService,
+ {provide: XHRBackend, useClass: MockBackend},
+ {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+ ]
+ })
+ .compileComponents();
+ }));
+
+ it('can instantiate service when inject service',
+ inject([GrokValidationService], (service: GrokValidationService) => {
+ expect(service instanceof GrokValidationService).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 GrokValidationService(http, config);
+ expect(service instanceof GrokValidationService).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 grokValidationService: GrokValidationService;
+ let mockBackend: MockBackend;
+ let grokValidation = new GrokValidation();
+ grokValidation.statement = 'statement';
+ grokValidation.sampleData = 'sampleData';
+ grokValidation.results = {'results': 'results'};
+ let grokList = ['pattern'];
+ let grokStatement = 'grok statement';
+ let grokValidationResponse: Response;
+ let grokListResponse: Response;
+ let grokGetStatementResponse: Response;
+
+ beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => {
+ mockBackend = be;
+ grokValidationService = new GrokValidationService(http, config);
+ grokValidationResponse = new Response(new ResponseOptions({status: 200, body: grokValidation}));
+ grokListResponse = new Response(new ResponseOptions({status: 200, body: grokList}));
+ grokGetStatementResponse = new Response(new ResponseOptions({status: 200, body: grokStatement}));
+ }));
+
+ it('validate', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(grokValidationResponse));
+
+ grokValidationService.validate(grokValidation).subscribe(
+ result => {
+ expect(result).toEqual(grokValidation);
+ }, error => console.log(error));
+ })));
+
+ it('list', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(grokListResponse));
+ grokValidationService.list().subscribe(
+ results => {
+ expect(results).toEqual(grokList);
+ }, error => console.log(error));
+ })));
+
+ it('getStatement', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(grokGetStatementResponse));
+ grokValidationService.getStatement('/path').subscribe(
+ results => {
+ expect(results).toEqual(grokStatement);
+ }, error => console.log(error));
+ })));
+ });
+
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/grok-validation.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/grok-validation.service.ts b/metron-interface/metron-config/src/app/service/grok-validation.service.ts
new file mode 100644
index 0000000..bcdce82
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/grok-validation.service.ts
@@ -0,0 +1,56 @@
+/**
+ * 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, URLSearchParams} from '@angular/http';
+import {Observable} from 'rxjs/Observable';
+import {GrokValidation} from '../model/grok-validation';
+import {HttpUtil} from '../util/httpUtil';
+import {IAppConfig} from '../app.config.interface';
+import {APP_CONFIG} from '../app.config';
+
+@Injectable()
+export class GrokValidationService {
+ url = this.config.apiEndpoint + '/grok';
+ defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'};
+
+ constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) {
+
+ }
+
+ public validate(grokValidation: GrokValidation): Observable<GrokValidation> {
+ return this.http.post(this.url + '/validate', JSON.stringify(grokValidation),
+ 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 getStatement(path: string): Observable<string> {
+ let params: URLSearchParams = new URLSearchParams();
+ params.set('path', path);
+ return this.http.get(this.url + '/get/statement', new RequestOptions({headers: new Headers(this.defaultHeaders), search: params}))
+ .map(HttpUtil.extractString)
+ .catch(HttpUtil.handleError);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts b/metron-interface/metron-config/src/app/service/hdfs.service.spec.ts
new file mode 100644
index 0000000..16196ab
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/hdfs.service.spec.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 {async, inject, TestBed} from '@angular/core/testing';
+import {MockBackend, MockConnection} from '@angular/http/testing';
+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 {HdfsService} from './hdfs.service';
+
+describe('HdfsService', () => {
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpModule],
+ providers: [
+ HdfsService,
+ {provide: XHRBackend, useClass: MockBackend},
+ {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+ ]
+ })
+ .compileComponents();
+ }));
+
+ it('can instantiate service when inject service',
+ inject([HdfsService], (service: HdfsService) => {
+ expect(service instanceof HdfsService).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 HdfsService(http, config);
+ expect(service instanceof HdfsService).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 hdfsService: HdfsService;
+ let mockBackend: MockBackend;
+ let fileList = ['file1', 'file2'];
+ let contents = 'file contents';
+ let listResponse: Response;
+ let readResponse: Response;
+ let postResponse: Response;
+ let deleteResponse: Response;
+
+ beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => {
+ mockBackend = be;
+ hdfsService = new HdfsService(http, config);
+ listResponse = new Response(new ResponseOptions({status: 200, body: fileList}));
+ readResponse = new Response(new ResponseOptions({status: 200, body: contents}));
+ postResponse = new Response(new ResponseOptions({status: 200}));
+ deleteResponse = new Response(new ResponseOptions({status: 200}));
+ }));
+
+ it('list', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(listResponse));
+ hdfsService.list('/path').subscribe(
+ result => {
+ expect(result).toEqual(fileList);
+ }, error => console.log(error));
+ })));
+
+ it('read', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(readResponse));
+ hdfsService.read('/path').subscribe(
+ result => {
+ expect(result).toEqual(contents);
+ }, error => console.log(error));
+ })));
+
+ it('post', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(postResponse));
+ hdfsService.post('/path', contents).subscribe(
+ result => {
+ expect(result.status).toEqual(200);
+ }, error => console.log(error));
+ })));
+
+ it('deleteFile', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deleteResponse));
+ hdfsService.deleteFile('/path').subscribe(
+ result => {
+ expect(result.status).toEqual(200);
+ }, error => console.log(error));
+ })));
+ });
+
+
+});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/hdfs.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/hdfs.service.ts b/metron-interface/metron-config/src/app/service/hdfs.service.ts
new file mode 100644
index 0000000..4e4b808
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/hdfs.service.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.
+ */
+import {Injectable, Inject} from '@angular/core';
+import {Http, Headers, RequestOptions, Response, URLSearchParams} from '@angular/http';
+import {Observable} from 'rxjs/Observable';
+import {HttpUtil} from '../util/httpUtil';
+import {IAppConfig} from '../app.config.interface';
+import {APP_CONFIG} from '../app.config';
+
+@Injectable()
+export class HdfsService {
+ url = this.config.apiEndpoint + '/hdfs';
+ defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'};
+
+ constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) {
+ }
+
+ public list(path: string): Observable<string[]> {
+ let params: URLSearchParams = new URLSearchParams();
+ params.set('path', path);
+ return this.http.get(this.url + '/list', new RequestOptions({headers: new Headers(this.defaultHeaders), search: params}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public read(path: string): Observable<string> {
+ let params: URLSearchParams = new URLSearchParams();
+ params.set('path', path);
+ return this.http.get(this.url , new RequestOptions({headers: new Headers(this.defaultHeaders), search: params}))
+ .map(HttpUtil.extractString)
+ .catch(HttpUtil.handleError);
+ }
+
+ public post(path: string, contents: string): Observable<Response> {
+ let params: URLSearchParams = new URLSearchParams();
+ params.set('path', path);
+ return this.http.post(this.url, contents, new RequestOptions({headers: new Headers(this.defaultHeaders), search: params}))
+ .catch(HttpUtil.handleError);
+ }
+
+ public deleteFile(path: string): Observable<Response> {
+ let params: URLSearchParams = new URLSearchParams();
+ params.set('path', path);
+ return this.http.delete(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders), search: params}))
+ .catch(HttpUtil.handleError);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/kafka.service.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/kafka.service.spec.ts b/metron-interface/metron-config/src/app/service/kafka.service.spec.ts
new file mode 100644
index 0000000..e6f1d7f
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/kafka.service.spec.ts
@@ -0,0 +1,114 @@
+/**
+ * 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 {KafkaService} from './kafka.service';
+import {KafkaTopic} from '../model/kafka-topic';
+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('KafkaService', () => {
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpModule],
+ providers: [
+ KafkaService,
+ {provide: XHRBackend, useClass: MockBackend},
+ {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+ ]
+ })
+ .compileComponents();
+ }));
+
+ it('can instantiate service when inject service',
+ inject([KafkaService], (service: KafkaService) => {
+ expect(service instanceof KafkaService).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 KafkaService(http, config);
+ expect(service instanceof KafkaService).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 kafkaService: KafkaService;
+ let mockBackend: MockBackend;
+ let kafkaTopic = new KafkaTopic();
+ kafkaTopic.name = 'bro';
+ kafkaTopic.numPartitions = 1;
+ kafkaTopic.replicationFactor = 1;
+ let sampleMessage = 'sample message';
+ let kafkaResponse: Response;
+ let kafkaListResponse: Response;
+ let sampleMessageResponse: Response;
+
+ beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => {
+ mockBackend = be;
+ kafkaService = new KafkaService(http, config);
+ kafkaResponse = new Response(new ResponseOptions({status: 200, body: kafkaTopic}));
+ kafkaListResponse = new Response(new ResponseOptions({status: 200, body: [kafkaTopic]}));
+ sampleMessageResponse = new Response(new ResponseOptions({status: 200, body: sampleMessage}));
+ }));
+
+ it('post', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(kafkaResponse));
+
+ kafkaService.post(kafkaTopic).subscribe(
+ result => {
+ expect(result).toEqual(kafkaTopic);
+ }, error => console.log(error));
+ })));
+
+ it('get', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(kafkaResponse));
+
+ kafkaService.get('bro').subscribe(
+ result => {
+ expect(result).toEqual(kafkaTopic);
+ }, error => console.log(error));
+ })));
+
+ it('list', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(kafkaListResponse));
+
+ kafkaService.list().subscribe(
+ result => {
+ expect(result).toEqual([kafkaTopic]);
+ }, error => console.log(error));
+ })));
+
+ it('sample', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sampleMessageResponse));
+ kafkaService.sample('bro').subscribe(
+ result => {
+ expect(result).toEqual(sampleMessage);
+ }, error => console.log(error));
+ })));
+ });
+
+});
+
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/kafka.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/kafka.service.ts b/metron-interface/metron-config/src/app/service/kafka.service.ts
new file mode 100644
index 0000000..ac02366
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/kafka.service.ts
@@ -0,0 +1,59 @@
+/**
+ * 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 {KafkaTopic} from '../model/kafka-topic';
+import {HttpUtil} from '../util/httpUtil';
+import {IAppConfig} from '../app.config.interface';
+import {APP_CONFIG} from '../app.config';
+
+@Injectable()
+export class KafkaService {
+ url = this.config.apiEndpoint + '/kafka/topic';
+ defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'};
+
+ constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) {
+
+ }
+
+ public post(kafkaTopic: KafkaTopic): Observable<KafkaTopic> {
+ return this.http.post(this.url, JSON.stringify(kafkaTopic), new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public get(name: string): Observable<KafkaTopic> {
+ return this.http.get(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public list(): Observable<string[]> {
+ return this.http.get(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public sample(name: string): Observable<string> {
+ return this.http.get(this.url + '/' + name + '/sample', new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractString)
+ .catch(HttpUtil.handleError);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.ts
new file mode 100644
index 0000000..89863ee
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.spec.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 {async, inject, TestBed} from '@angular/core/testing';
+import {MockBackend, MockConnection} from '@angular/http/testing';
+import {SensorEnrichmentConfigService} from './sensor-enrichment-config.service';
+import {SensorEnrichmentConfig, EnrichmentConfig} from '../model/sensor-enrichment-config';
+import {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http';
+import '../rxjs-operators';
+import {METRON_REST_CONFIG, APP_CONFIG} from '../app.config';
+import {IAppConfig} from '../app.config.interface';
+
+describe('SensorEnrichmentConfigService', () => {
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpModule],
+ providers: [
+ SensorEnrichmentConfigService,
+ {provide: XHRBackend, useClass: MockBackend},
+ {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+ ]
+ })
+ .compileComponents();
+ }));
+
+ it('can instantiate service when inject service',
+ inject([SensorEnrichmentConfigService], (service: SensorEnrichmentConfigService) => {
+ expect(service instanceof SensorEnrichmentConfigService).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 SensorEnrichmentConfigService(http, config);
+ expect(service instanceof SensorEnrichmentConfigService).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 sensorEnrichmentConfigService: SensorEnrichmentConfigService;
+ let mockBackend: MockBackend;
+ let sensorEnrichmentConfig1 = new SensorEnrichmentConfig();
+ let enrichmentConfig1 = new EnrichmentConfig();
+ enrichmentConfig1.fieldMap = {'geo': ['ip_dst_addr'], 'host': ['ip_dst_addr']};
+ sensorEnrichmentConfig1.enrichment.fieldMap = enrichmentConfig1;
+ let sensorEnrichmentConfig2 = new SensorEnrichmentConfig();
+ let enrichmentConfig2 = new EnrichmentConfig();
+ enrichmentConfig1.fieldMap = {'whois': ['ip_dst_addr'], 'host': ['ip_src_addr']};
+ sensorEnrichmentConfig2.enrichment = enrichmentConfig2;
+ let availableEnrichments: string[] = ['geo', 'host', 'whois'];
+ let availableThreatTriageAggregators: string[] = ['MAX', 'MIN', 'SUM', 'MEAN', 'POSITIVE_MEAN'];
+ let sensorEnrichmentConfigResponse: Response;
+ let sensorEnrichmentConfigsResponse: Response;
+ let availableEnrichmentsResponse: Response;
+ let availableThreatTriageAggregatorsResponse: Response;
+ let deleteResponse: Response;
+
+ beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => {
+ mockBackend = be;
+ sensorEnrichmentConfigService = new SensorEnrichmentConfigService(http, config);
+ sensorEnrichmentConfigResponse = new Response(new ResponseOptions({status: 200, body: sensorEnrichmentConfig1}));
+ sensorEnrichmentConfigsResponse = new Response(new ResponseOptions({status: 200, body: [sensorEnrichmentConfig1,
+ sensorEnrichmentConfig2]}));
+ availableEnrichmentsResponse = new Response(new ResponseOptions({status: 200, body: availableEnrichments}));
+ availableThreatTriageAggregatorsResponse = new Response(new ResponseOptions({status: 200, body: availableThreatTriageAggregators}));
+ deleteResponse = new Response(new ResponseOptions({status: 200}));
+ }));
+
+ it('post', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorEnrichmentConfigResponse));
+
+ sensorEnrichmentConfigService.post('bro', sensorEnrichmentConfig1).subscribe(
+ result => {
+ expect(result).toEqual(sensorEnrichmentConfig1);
+ }, error => console.log(error));
+ })));
+
+ it('get', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorEnrichmentConfigResponse));
+
+ sensorEnrichmentConfigService.get('bro').subscribe(
+ result => {
+ expect(result).toEqual(sensorEnrichmentConfig1);
+ }, error => console.log(error));
+ })));
+
+ it('getAll', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorEnrichmentConfigsResponse));
+
+ sensorEnrichmentConfigService.getAll().subscribe(
+ results => {
+ expect(results).toEqual([sensorEnrichmentConfig1, sensorEnrichmentConfig2]);
+ }, error => console.log(error));
+ })));
+
+ it('getAvailableEnrichments', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(availableEnrichmentsResponse));
+
+ sensorEnrichmentConfigService.getAvailableEnrichments().subscribe(
+ results => {
+ expect(results).toEqual(availableEnrichments);
+ }, error => console.log(error));
+ })));
+
+ it('getAvailableThreatTriageAggregators', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(availableThreatTriageAggregatorsResponse));
+
+ sensorEnrichmentConfigService.getAvailableThreatTriageAggregators().subscribe(
+ results => {
+ expect(results).toEqual(availableThreatTriageAggregators);
+ }, error => console.log(error));
+ })));
+
+ it('deleteSensorEnrichments', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deleteResponse));
+
+ sensorEnrichmentConfigService.deleteSensorEnrichments('bro').subscribe(result => {
+ expect(result.status).toEqual(200);
+ });
+ })));
+ });
+
+});
+
+
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts
new file mode 100644
index 0000000..90c314b
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/sensor-enrichment-config.service.ts
@@ -0,0 +1,71 @@
+/**
+ * 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 {SensorEnrichmentConfig} from '../model/sensor-enrichment-config';
+import {HttpUtil} from '../util/httpUtil';
+import {IAppConfig} from '../app.config.interface';
+import {APP_CONFIG} from '../app.config';
+
+@Injectable()
+export class SensorEnrichmentConfigService {
+ url = this.config.apiEndpoint + '/sensor/enrichment/config';
+ defaultHeaders = {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'};
+
+ constructor(private http: Http, @Inject(APP_CONFIG) private config: IAppConfig) {
+ }
+
+ public post(name: string, sensorEnrichmentConfig: SensorEnrichmentConfig): Observable<SensorEnrichmentConfig> {
+ return this.http.post(this.url + '/' + name, JSON.stringify(sensorEnrichmentConfig),
+ new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public get(name: string): Observable<SensorEnrichmentConfig> {
+ return this.http.get(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public getAll(): Observable<SensorEnrichmentConfig[]> {
+ return this.http.get(this.url, new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public deleteSensorEnrichments(name: string): Observable<Response> {
+ return this.http.delete(this.url + '/' + name, new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .catch(HttpUtil.handleError);
+ }
+
+ public getAvailableEnrichments(): Observable<string[]> {
+ return this.http.get(this.url + '/list/available/enrichments', new RequestOptions({headers: new Headers(this.defaultHeaders)}))
+ .map(HttpUtil.extractData)
+ .catch(HttpUtil.handleError);
+ }
+
+ public getAvailableThreatTriageAggregators(): Observable<string[]> {
+ return this.http.get(this.url + '/list/available/threat/triage/aggregators',
+ 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/sensor-indexing-config.service.spec.ts
----------------------------------------------------------------------
diff --git a/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts
new file mode 100644
index 0000000..3640162
--- /dev/null
+++ b/metron-interface/metron-config/src/app/service/sensor-indexing-config.service.spec.ts
@@ -0,0 +1,118 @@
+/**
+ * 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 {HttpModule, XHRBackend, Response, ResponseOptions, Http} from '@angular/http';
+import '../rxjs-operators';
+import {METRON_REST_CONFIG, APP_CONFIG} from '../app.config';
+import {IAppConfig} from '../app.config.interface';
+import {SensorIndexingConfigService} from './sensor-indexing-config.service';
+import {IndexingConfigurations} from '../model/sensor-indexing-config';
+
+describe('SensorIndexingConfigService', () => {
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpModule],
+ providers: [
+ SensorIndexingConfigService,
+ {provide: XHRBackend, useClass: MockBackend},
+ {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}
+ ]
+ })
+ .compileComponents();
+ }));
+
+ it('can instantiate service when inject service',
+ inject([SensorIndexingConfigService], (service: SensorIndexingConfigService) => {
+ expect(service instanceof SensorIndexingConfigService).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 SensorIndexingConfigService(http, config);
+ expect(service instanceof SensorIndexingConfigService).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 sensorIndexingConfigService: SensorIndexingConfigService;
+ let mockBackend: MockBackend;
+ let sensorIndexingConfig1 = new IndexingConfigurations();
+ sensorIndexingConfig1.hdfs.index = 'squid';
+ sensorIndexingConfig1.hdfs.batchSize = 1;
+ let sensorIndexingConfig2 = new IndexingConfigurations();
+ sensorIndexingConfig2.hdfs.index = 'yaf';
+ sensorIndexingConfig2.hdfs.batchSize = 2;
+ let sensorIndexingConfigResponse: Response;
+ let sensorIndexingConfigsResponse: Response;
+ let deleteResponse: Response;
+
+ beforeEach(inject([Http, XHRBackend, APP_CONFIG], (http: Http, be: MockBackend, config: IAppConfig) => {
+ mockBackend = be;
+ sensorIndexingConfigService = new SensorIndexingConfigService(http, config);
+ sensorIndexingConfigResponse = new Response(new ResponseOptions({status: 200, body: sensorIndexingConfig1}));
+ sensorIndexingConfigsResponse = new Response(new ResponseOptions({status: 200, body: [sensorIndexingConfig1,
+ sensorIndexingConfig2]}));
+ deleteResponse = new Response(new ResponseOptions({status: 200}));
+ }));
+
+ it('post', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorIndexingConfigResponse));
+
+ sensorIndexingConfigService.post('squid', sensorIndexingConfig1).subscribe(
+ result => {
+ expect(result).toEqual(sensorIndexingConfig1);
+ }, error => console.log(error));
+ })));
+
+ it('get', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorIndexingConfigResponse));
+
+ sensorIndexingConfigService.get('squid').subscribe(
+ result => {
+ expect(result).toEqual(sensorIndexingConfig1);
+ }, error => console.log(error));
+ })));
+
+ it('getAll', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(sensorIndexingConfigsResponse));
+
+ sensorIndexingConfigService.getAll().subscribe(
+ results => {
+ expect(results).toEqual([sensorIndexingConfig1, sensorIndexingConfig2]);
+ }, error => console.log(error));
+ })));
+
+ it('deleteSensorEnrichments', async(inject([], () => {
+ mockBackend.connections.subscribe((c: MockConnection) => c.mockRespond(deleteResponse));
+
+ sensorIndexingConfigService.deleteSensorIndexingConfig('squid').subscribe(result => {
+ expect(result.status).toEqual(200);
+ });
+ })));
+ });
+
+});
+
+