http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.component.spec.ts new file mode 100644 index 0000000..d2066ea --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.component.spec.ts @@ -0,0 +1,523 @@ +/** + * 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 */ +/* tslint:disable:max-line-length */ + +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import {Http} from '@angular/http'; +import {SimpleChanges, SimpleChange} from '@angular/core'; +import {SensorParserConfigService} from '../../service/sensor-parser-config.service'; +import {StellarService} from '../../service/stellar.service'; +import {MetronAlerts} from '../../shared/metron-alerts'; +import {SensorFieldSchemaModule} from './sensor-field-schema.module'; +import {SensorFieldSchemaComponent, FieldSchemaRow} from './sensor-field-schema.component'; +import {KafkaService} from '../../service/kafka.service'; +import {Observable} from 'rxjs/Observable'; +import {StellarFunctionDescription} from '../../model/stellar-function-description'; +import {SensorParserConfig} from '../../model/sensor-parser-config'; +import {SensorEnrichmentConfig, EnrichmentConfig, ThreatIntelConfig} from '../../model/sensor-enrichment-config'; +import {ParseMessageRequest} from '../../model/parse-message-request'; +import {AutocompleteOption} from '../../model/autocomplete-option'; +import {FieldTransformer} from '../../model/field-transformer'; +import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service'; + + +class MockSensorParserConfigService { + + parseMessage(parseMessageRequest: ParseMessageRequest): Observable<{}> { + let parsedJson = { + 'elapsed': 415, + 'code': 200, + 'ip_dst_addr': '207.109.73.154', + 'original_string': '1467011157.401 415 127.0.0.1 TCP_MISS/200 337891 GET http://www.aliexpress.com/', + 'method': 'GET', + 'bytes': 337891, + 'action': 'TCP_MISS', + 'ip_src_addr': '127.0.0.1', + 'url': 'http://www.aliexpress.com/af/shoes.html?', + 'timestamp': '1467011157.401' + }; + return Observable.create((observable) => { + observable.next(parsedJson); + observable.complete(); + }); + } +} + +class MockTransformationValidationService { + public listSimpleFunctions(): Observable<StellarFunctionDescription[]> { + let stellarFunctionDescription: StellarFunctionDescription[] = []; + stellarFunctionDescription.push(new StellarFunctionDescription('TO_LOWER', 'TO_LOWER description', ['input - input field'])); + stellarFunctionDescription.push(new StellarFunctionDescription('TO_UPPER', 'TO_UPPER description', ['input - input field'])); + stellarFunctionDescription.push(new StellarFunctionDescription('TRIM', 'Lazy to copy desc', ['input - input field'])); + return Observable.create((observer) => { + observer.next(stellarFunctionDescription); + observer.complete(); + }); + } +} + +class MockSensorEnrichmentConfigService { + public getAvailableEnrichments(): Observable<string[]> { + return Observable.create((observer) => { + observer.next(['geo', 'host', 'whois']); + observer.complete(); + }); + } +} + +class MockKafkaService { + +} + +describe('Component: SensorFieldSchema', () => { + let component: SensorFieldSchemaComponent; + let sensorEnrichmentConfigService: SensorEnrichmentConfigService; + let sensorParserConfigService: SensorParserConfigService; + let fixture: ComponentFixture<SensorFieldSchemaComponent>; + let transformationValidationService: StellarService; + + let squidSensorConfigJson = { + 'parserClassName': 'org.apache.metron.parsers.GrokParser', + 'sensorTopic': 'squid', + 'parserConfig': { + 'grokPath': 'target/patterns/squid', + 'grokStatement': '%{NUMBER:timestamp} %{INT:elapsed} %{IPV4:ip_src_addr} %{WORD:action}/%{NUMBER:code} ' + + '%{NUMBER:bytes} %{WORD:method} %{NOTSPACE:url} - %{WORD:UNWANTED}\\/%{IPV4:ip_dst_addr} ' + + '%{WORD:UNWANTED}\\/%{WORD:UNWANTED}' + }, + 'fieldTransformations': [ + { + 'input': [], + 'output': ['method'], + 'transformation': 'STELLAR', + 'config': { + 'method': 'TRIM(TO_LOWER(method))' + } + }, + { + 'input': ['code'], + 'output': null, + 'transformation': 'REMOVE', + 'config': { + 'condition': 'exists(field2)' + } + }, + { + 'input': ['ip_src_addr'], + 'output': null, + 'transformation': 'REMOVE' + } + ] + }; + let squidEnrichmentJson = { + 'index': 'squid', + 'batchSize': 1, + 'enrichment': { + 'fieldMap': { + 'geo': ['ip_dst_addr', 'ip_src_addr'], + 'host': ['ip_dst_addr'], + 'whois': ['ip_src_addr'] + }, + 'fieldToTypeMap': {}, + 'config': {} + }, + 'threatIntel': { + 'fieldMap': { + 'hbaseThreatIntel': ['ip_dst_addr'] + }, + 'fieldToTypeMap': { + 'ip_dst_addr': ['malicious_ip'] + }, + 'config': {}, + 'triageConfig': { + 'riskLevelRules': {}, + 'aggregator': 'MAX', + 'aggregationConfig': {} + } + }, + 'configuration': {} + }; + let sensorParserConfig = Object.assign(new SensorParserConfig(), squidSensorConfigJson); + let sensorEnrichmentConfig = Object.assign(new SensorEnrichmentConfig(), squidEnrichmentJson); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [SensorFieldSchemaModule], + providers: [ + MetronAlerts, + {provide: Http}, + {provide: KafkaService, useClass: MockKafkaService}, + {provide: SensorEnrichmentConfigService, useClass: MockSensorEnrichmentConfigService}, + {provide: SensorParserConfigService, useClass: MockSensorParserConfigService}, + {provide: StellarService, useClass: MockTransformationValidationService}, + + ] + }).compileComponents() + .then(() => { + fixture = TestBed.createComponent(SensorFieldSchemaComponent); + component = fixture.componentInstance; + sensorParserConfigService = fixture.debugElement.injector.get(SensorParserConfigService); + transformationValidationService = fixture.debugElement.injector.get(StellarService); + sensorEnrichmentConfigService = fixture.debugElement.injector.get(SensorEnrichmentConfigService); + }); + })); + + it('should create an instance', () => { + expect(component).toBeDefined(); + fixture.destroy(); + }); + + + + it('should read TransformFunctions, EnrichmentFunctions, ThreatIntelfunctions', () => { + component.ngOnInit(); + + expect(component.transformOptions.length).toEqual(3); + expect(Object.keys(component.transformFunctions).length).toEqual(3); + expect(component.enrichmentOptions.length).toEqual(3); + expect(component.threatIntelOptions.length).toEqual(1); + + fixture.destroy(); + }); + + it('should call getSampleData if showFieldSchema', () => { + spyOn(component.sampleData, 'getNextSample'); + + let changes: SimpleChanges = { + 'showFieldSchema': new SimpleChange(false, true) + }; + component.ngOnChanges(changes); + expect(component.sampleData.getNextSample['calls'].count()).toEqual(1); + + changes = { + 'showFieldSchema': new SimpleChange(true, false) + }; + component.ngOnChanges(changes); + expect(component.sampleData.getNextSample['calls'].count()).toEqual(1); + + fixture.destroy(); + }); + + it('should return isSimple function', () => { + component.ngOnInit(); + + expect(component.isSimpleFunction(['TO_LOWER', 'TO_UPPER'])).toEqual(true); + expect(component.isSimpleFunction(['TO_LOWER', 'TO_UPPER', 'TEST'])).toEqual(false); + + fixture.destroy(); + }); + + it('should create FieldSchemaRows', () => { + component.ngOnInit(); + + component.sensorParserConfig = sensorParserConfig; + component.sensorEnrichmentConfig = sensorEnrichmentConfig; + component.onSampleDataChanged('DoctorStrange'); + component.createFieldSchemaRows(); + + expect(component.fieldSchemaRows.length).toEqual(10); + expect(component.savedFieldSchemaRows.length).toEqual(10); + + let methodFieldSchemaRow: FieldSchemaRow = component.fieldSchemaRows.filter(row => row.inputFieldName === 'method')[0]; + expect(methodFieldSchemaRow).toBeDefined(); + expect(methodFieldSchemaRow.transformConfigured.length).toEqual(2); + expect(methodFieldSchemaRow.enrichmentConfigured.length).toEqual(0); + expect(methodFieldSchemaRow.threatIntelConfigured.length).toEqual(0); + + let ipSrcAddrFieldSchemaRow: FieldSchemaRow = component.fieldSchemaRows.filter(row => row.inputFieldName === 'ip_src_addr')[0]; + expect(ipSrcAddrFieldSchemaRow).toBeDefined(); + expect(ipSrcAddrFieldSchemaRow.transformConfigured.length).toEqual(0); + expect(ipSrcAddrFieldSchemaRow.enrichmentConfigured.length).toEqual(2); + expect(ipSrcAddrFieldSchemaRow.threatIntelConfigured.length).toEqual(0); + + let ipDstAddrFieldSchemaRow: FieldSchemaRow = component.fieldSchemaRows.filter(row => row.inputFieldName === 'ip_dst_addr')[0]; + expect(ipDstAddrFieldSchemaRow).toBeDefined(); + expect(ipDstAddrFieldSchemaRow.transformConfigured.length).toEqual(0); + expect(ipDstAddrFieldSchemaRow.enrichmentConfigured.length).toEqual(2); + expect(ipDstAddrFieldSchemaRow.threatIntelConfigured.length).toEqual(1); + + let codeSchemaRow: FieldSchemaRow = component.fieldSchemaRows.filter(row => row.inputFieldName === 'code')[0]; + expect(codeSchemaRow).toBeDefined(); + expect(codeSchemaRow.isRemoved).toEqual(true); + expect(codeSchemaRow.conditionalRemove).toEqual(true); + expect(codeSchemaRow.transformConfigured.length).toEqual(0); + expect(codeSchemaRow.enrichmentConfigured.length).toEqual(0); + expect(codeSchemaRow.threatIntelConfigured.length).toEqual(0); + + fixture.destroy(); + }); + + it('should return getChanges', () => { + let fieldSchemaRow = new FieldSchemaRow('method'); + fieldSchemaRow.transformConfigured = []; + fieldSchemaRow.enrichmentConfigured = [new AutocompleteOption('GEO'), new AutocompleteOption('WHOIS')]; + fieldSchemaRow.threatIntelConfigured = [new AutocompleteOption('MALICIOUS-IP')]; + + expect(component.getChanges(fieldSchemaRow)).toEqual('Enrichments: GEO, WHOIS <br> Threat Intel: MALICIOUS-IP'); + + fieldSchemaRow.transformConfigured = [new AutocompleteOption('TO_STRING')]; + fieldSchemaRow.enrichmentConfigured = [new AutocompleteOption('GEO')]; + fieldSchemaRow.threatIntelConfigured = [new AutocompleteOption('MALICIOUS-IP'), new AutocompleteOption('MALICIOUS-IP')]; + + expect(component.getChanges(fieldSchemaRow)).toEqual('Transforms: TO_STRING(method) <br> Enrichments: GEO <br> Threat Intel: MALICIOUS-IP, MALICIOUS-IP'); + + + fieldSchemaRow.transformConfigured = [new AutocompleteOption('TO_STRING'), new AutocompleteOption('TO_STRING')]; + fieldSchemaRow.enrichmentConfigured = []; + fieldSchemaRow.threatIntelConfigured = [new AutocompleteOption('MALICIOUS-IP'), new AutocompleteOption('MALICIOUS-IP')]; + + expect(component.getChanges(fieldSchemaRow)).toEqual('Transforms: TO_STRING(TO_STRING(method)) <br> Threat Intel: MALICIOUS-IP, MALICIOUS-IP'); + + fieldSchemaRow.transformConfigured = [new AutocompleteOption('TO_STRING'), new AutocompleteOption('TO_STRING')]; + fieldSchemaRow.enrichmentConfigured = []; + fieldSchemaRow.threatIntelConfigured = []; + + expect(component.getChanges(fieldSchemaRow)).toEqual('Transforms: TO_STRING(TO_STRING(method)) <br> '); + + fieldSchemaRow.transformConfigured = [new AutocompleteOption('TO_STRING'), new AutocompleteOption('TO_STRING')]; + fieldSchemaRow.isRemoved = true; + expect(component.getChanges(fieldSchemaRow)).toEqual('Disabled'); + + fixture.destroy(); + }); + + it('should call appropriate functions when onSampleDataChanged is called ', () => { + let returnSuccess = true; + spyOn(component, 'createFieldSchemaRows'); + spyOn(component, 'onSampleDataNotAvailable'); + spyOn(sensorParserConfigService, 'parseMessage').and.callFake(function(parseMessageRequest: ParseMessageRequest) { + expect(parseMessageRequest.sensorParserConfig.parserConfig['patternLabel']).toEqual(parseMessageRequest.sensorParserConfig.sensorTopic.toUpperCase()); + expect(parseMessageRequest.sensorParserConfig.parserConfig['grokPath']).toEqual('./' + parseMessageRequest.sensorParserConfig.sensorTopic); + if (returnSuccess) { + return Observable.create(observer => { + observer.next({'a': 'b', 'c': 'd'}); + observer.complete(); + }); + } + return Observable.throw('Error'); + }); + + component.sensorParserConfig = sensorParserConfig; + component.sensorParserConfig.parserConfig['patternLabel'] = null; + component.onSampleDataChanged('DoctorStrange'); + expect(component.parserResult).toEqual({'a': 'b', 'c': 'd'}); + expect(component.createFieldSchemaRows).toHaveBeenCalled(); + expect(component.onSampleDataNotAvailable).not.toHaveBeenCalled(); + + returnSuccess = false; + component.parserResult = {}; + component.onSampleDataChanged('DoctorStrange'); + expect(component.parserResult).toEqual({}); + expect(component.onSampleDataNotAvailable).toHaveBeenCalled(); + expect(component.onSampleDataNotAvailable['calls'].count()).toEqual(1); + + fixture.destroy(); + }); + + it('should onSampleDataChanged available and onSampleDataNotAvailable ', () => { + let returnSuccess = true; + spyOn(component, 'createFieldSchemaRows'); + + component.onSampleDataNotAvailable(); + expect(component.createFieldSchemaRows['calls'].count()).toEqual(1); + + fixture.destroy(); + }); + + it('should call onSaveChange on onRemove/onEnable ', () => { + spyOn(component, 'onSave'); + + let fieldSchemaRow = new FieldSchemaRow('method'); + fieldSchemaRow.outputFieldName = 'copy-of-method'; + fieldSchemaRow.preview = 'TRIM(TO_LOWER(method))'; + fieldSchemaRow.isRemoved = false; + + component.savedFieldSchemaRows = [fieldSchemaRow]; + + let removeFieldSchemaRow = JSON.parse(JSON.stringify(fieldSchemaRow)); + component.onRemove(removeFieldSchemaRow); + expect(removeFieldSchemaRow.isRemoved).toEqual(true); + expect(component.savedFieldSchemaRows[0].isRemoved).toEqual(true); + expect(component.onSave['calls'].count()).toEqual(1); + + fieldSchemaRow.isRemoved = true; + let enableFieldSchemaRow = JSON.parse(JSON.stringify(fieldSchemaRow)); + component.onEnable(enableFieldSchemaRow); + expect(fieldSchemaRow.isRemoved).toEqual(false); + expect(component.savedFieldSchemaRows[0].isRemoved).toEqual(false); + expect(component.onSave['calls'].count()).toEqual(2); + + fixture.destroy(); + }); + + it('should revert changes on cancel ', () => { + let fieldSchemaRow = new FieldSchemaRow('method'); + fieldSchemaRow.showConfig = true; + fieldSchemaRow.outputFieldName = 'method'; + fieldSchemaRow.preview = 'TRIM(TO_LOWER(method))'; + fieldSchemaRow.isRemoved = false; + fieldSchemaRow.isSimple = true; + fieldSchemaRow.transformConfigured = [new AutocompleteOption('TO_LOWER'), new AutocompleteOption('TRIM')]; + + component.savedFieldSchemaRows.push(fieldSchemaRow); + + component.onCancelChange(fieldSchemaRow); + expect(fieldSchemaRow.showConfig).toEqual(false); + + component.hideFieldSchema.emit = jasmine.createSpy('emit'); + component.onCancel(); + expect(component.hideFieldSchema.emit).toHaveBeenCalled(); + + fixture.destroy(); + }); + + it('should return formatted function on createTransformFunction call ', () => { + let fieldSchemaRow = new FieldSchemaRow('method'); + fieldSchemaRow.transformConfigured = [new AutocompleteOption('TRIM'), new AutocompleteOption('TO_STRING')]; + + expect(component.createTransformFunction(fieldSchemaRow)).toEqual('TO_STRING(TRIM(method))'); + + fixture.destroy(); + }); + + it('should set preview value for FieldSchemaRow ', () => { + let fieldSchemaRow = new FieldSchemaRow('method'); + fieldSchemaRow.transformConfigured = [new AutocompleteOption('TRIM'), new AutocompleteOption('TO_STRING')]; + + component.onTransformsChange(fieldSchemaRow); + expect(fieldSchemaRow.preview).toEqual('TO_STRING(TRIM(method))'); + + fieldSchemaRow.transformConfigured = [new AutocompleteOption('TRIM')]; + component.onTransformsChange(fieldSchemaRow); + expect(fieldSchemaRow.preview).toEqual('TRIM(method)'); + + fieldSchemaRow.transformConfigured = []; + component.onTransformsChange(fieldSchemaRow); + expect(fieldSchemaRow.preview).toEqual(''); + + fixture.destroy(); + }); + + it('isConditionalRemoveTransform ', () => { + let fieldTransformationJson = { + 'input': ['method'], + 'transformation': 'REMOVE', + 'config': + { + 'condition': 'IS_DOMAIN(elapsed)' + } + }; + let simpleFieldTransformationJson = { + 'input': ['method'], + 'transformation': 'REMOVE' + }; + let fieldTransformation: FieldTransformer = Object.assign(new FieldTransformer(), fieldTransformationJson); + expect(component.isConditionalRemoveTransform(fieldTransformation)).toEqual(true); + + let simpleFieldTransformation: FieldTransformer = Object.assign(new FieldTransformer(), simpleFieldTransformationJson); + expect(component.isConditionalRemoveTransform(simpleFieldTransformation)).toEqual(false); + + fixture.destroy(); + }); + + it('should save data ', () => { + let methodFieldSchemaRow = new FieldSchemaRow('method'); + methodFieldSchemaRow.outputFieldName = 'method'; + methodFieldSchemaRow.preview = 'TRIM(TO_LOWER(method))'; + methodFieldSchemaRow.isRemoved = false; + methodFieldSchemaRow.isSimple = true; + methodFieldSchemaRow.transformConfigured = [new AutocompleteOption('TO_LOWER'), new AutocompleteOption('TRIM')]; + + let elapsedFieldSchemaRow = new FieldSchemaRow('elapsed'); + elapsedFieldSchemaRow.outputFieldName = 'elapsed'; + elapsedFieldSchemaRow.preview = 'IS_DOMAIN(elapsed)'; + elapsedFieldSchemaRow.isRemoved = true; + elapsedFieldSchemaRow.isSimple = true; + elapsedFieldSchemaRow.transformConfigured = [new AutocompleteOption('IS_DOMAIN')]; + elapsedFieldSchemaRow.enrichmentConfigured = [new AutocompleteOption('host')]; + + let ipDstAddrFieldSchemaRow = new FieldSchemaRow('ip_dst_addr'); + ipDstAddrFieldSchemaRow.outputFieldName = 'ip_dst_addr'; + ipDstAddrFieldSchemaRow.preview = 'IS_DOMAIN(elapsed)'; + ipDstAddrFieldSchemaRow.isRemoved = false; + ipDstAddrFieldSchemaRow.isSimple = false; + ipDstAddrFieldSchemaRow.threatIntelConfigured = [new AutocompleteOption('malicious_ip')]; + ipDstAddrFieldSchemaRow.enrichmentConfigured = [new AutocompleteOption('host')]; + + let codeFieldSchemaRow = new FieldSchemaRow('code'); + codeFieldSchemaRow.outputFieldName = 'code'; + codeFieldSchemaRow.isRemoved = true; + codeFieldSchemaRow.conditionalRemove = true; + + component.savedFieldSchemaRows = [methodFieldSchemaRow, elapsedFieldSchemaRow, ipDstAddrFieldSchemaRow, codeFieldSchemaRow]; + + component.sensorParserConfig = new SensorParserConfig(); + component.sensorParserConfig.parserClassName = 'org.apache.metron.parsers.GrokParser'; + component.sensorParserConfig.sensorTopic = 'squid'; + + component.sensorParserConfig.fieldTransformations = [new FieldTransformer()]; + component.sensorParserConfig.fieldTransformations[0].transformation = 'REMOVE'; + component.sensorParserConfig.fieldTransformations[0].input = ['code']; + component.sensorParserConfig.fieldTransformations[0].config = {'condition': 'exists(method)'}; + + component.sensorEnrichmentConfig = new SensorEnrichmentConfig(); + component.sensorEnrichmentConfig.enrichment = new EnrichmentConfig(); + component.sensorEnrichmentConfig.threatIntel = new ThreatIntelConfig(); + component.sensorEnrichmentConfig.configuration = {}; + + component.onSave(); + + let fieldTransformationJson = { + 'output': ['method', 'elapsed'], + 'transformation': 'STELLAR', + 'config': + { + 'method': 'TRIM(TO_LOWER(method))', + 'elapsed': 'IS_DOMAIN(elapsed)' + } + }; + + let fieldTransformationRemoveJson = { + 'input': ['elapsed'], + 'transformation': 'REMOVE', + }; + + let conditionalFieldTransformationRemoveJson = { + 'input': ['code'], + 'transformation': 'REMOVE', + 'config': { + 'condition': 'exists(method)' + } + }; + + let fieldTransformation = Object.assign(new FieldTransformer(), fieldTransformationJson); + let fieldTransformationRemove = Object.assign(new FieldTransformer(), fieldTransformationRemoveJson); + let conditionalFieldTransformationRemove = Object.assign(new FieldTransformer(), conditionalFieldTransformationRemoveJson); + + expect(component.sensorParserConfig.fieldTransformations.length).toEqual(3); + let expectedStellar = component.sensorParserConfig.fieldTransformations.filter(transform => transform.transformation === 'STELLAR')[0]; + let expectedRemove = component.sensorParserConfig.fieldTransformations.filter(transform => transform.transformation === 'REMOVE' && !transform.config)[0]; + let expectedConditionalRemove = component.sensorParserConfig.fieldTransformations.filter(transform => transform.transformation === 'REMOVE' && transform.config)[0]; + expect(expectedStellar).toEqual(fieldTransformation); + expect(expectedRemove).toEqual(fieldTransformationRemove); + expect(expectedConditionalRemove).toEqual(conditionalFieldTransformationRemove); + + fixture.destroy(); + }); +});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.component.ts new file mode 100644 index 0000000..8d9dd11 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.component.ts @@ -0,0 +1,435 @@ +/** + * 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:max-line-length */ +import { Component, OnInit, Input, OnChanges, ViewChild, SimpleChanges, Output, EventEmitter } from '@angular/core'; +import {SensorParserConfig} from '../../model/sensor-parser-config'; +import {ParseMessageRequest} from '../../model/parse-message-request'; +import {SensorParserConfigService} from '../../service/sensor-parser-config.service'; +import {StellarService} from '../../service/stellar.service'; +import {AutocompleteOption} from '../../model/autocomplete-option'; +import {StellarFunctionDescription} from '../../model/stellar-function-description'; +import {SensorEnrichmentConfig, EnrichmentConfig, ThreatIntelConfig} from '../../model/sensor-enrichment-config'; +import {FieldTransformer} from '../../model/field-transformer'; +import {SampleDataComponent} from '../../shared/sample-data/sample-data.component'; +import {MetronAlerts} from '../../shared/metron-alerts'; +import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service'; + +export class FieldSchemaRow { + inputFieldName: string; + outputFieldName: string; + preview: string; + showConfig: boolean; + isRemoved: boolean; + isSimple: boolean; + isNew: boolean; + isParserGenerated: boolean; + conditionalRemove: boolean; + transformConfigured: AutocompleteOption[] = []; + enrichmentConfigured: AutocompleteOption[] = []; + threatIntelConfigured: AutocompleteOption[] = []; + + constructor(fieldName: string) { + this.inputFieldName = fieldName; + this.outputFieldName = fieldName; + this.conditionalRemove = false; + this.isParserGenerated = false; + this.showConfig = false; + this.isSimple = true; + this.isRemoved = false; + this.preview = ''; + } +} + +@Component({ + selector: 'metron-config-sensor-field-schema', + templateUrl: './sensor-field-schema.component.html', + styleUrls: ['./sensor-field-schema.component.scss'] +}) +export class SensorFieldSchemaComponent implements OnInit, OnChanges { + + @Input() sensorParserConfig: SensorParserConfig; + @Input() sensorEnrichmentConfig: SensorEnrichmentConfig; + @Input() showFieldSchema: boolean; + @Input() grokStatement: string; + + parserResult: any = {}; + fieldSchemaRows: FieldSchemaRow[] = []; + savedFieldSchemaRows: FieldSchemaRow[] = []; + + transformOptions: AutocompleteOption[] = []; + enrichmentOptions: AutocompleteOption[] = []; + threatIntelOptions: AutocompleteOption[] = []; + + transformFunctions: StellarFunctionDescription[]; + + @ViewChild(SampleDataComponent) sampleData: SampleDataComponent; + @Output() hideFieldSchema: EventEmitter<boolean> = new EventEmitter<boolean>(); + @Output() onFieldSchemaChanged: EventEmitter<boolean> = new EventEmitter<boolean>(); + + sampleThreatIntels: string[] = ['malicious_ip']; + + constructor(private sensorParserConfigService: SensorParserConfigService, + private transformationValidationService: StellarService, + private sensorEnrichmentConfigService: SensorEnrichmentConfigService, + private metronAlerts: MetronAlerts) { } + + ngOnChanges(changes: SimpleChanges) { + if (changes['showFieldSchema'] && changes['showFieldSchema'].currentValue) { + this.sampleData.getNextSample(); + } + } + + ngOnInit() { + this.getTransformFunctions(); + this.getEnrichmentFunctions(); + this.getThreatIntelfunctions(); + } + + getTransformFunctions() { + this.transformOptions = []; + + this.transformationValidationService.listSimpleFunctions().subscribe((result: StellarFunctionDescription[]) => { + this.transformFunctions = result; + for (let fun of result) { + this.transformOptions.push(new AutocompleteOption(fun.name, fun.name, fun.description)); + } + }); + } + + getEnrichmentFunctions() { + this.enrichmentOptions = []; + + this.sensorEnrichmentConfigService.getAvailableEnrichments().subscribe((result: string[]) => { + for (let fun of result) { + this.enrichmentOptions.push(new AutocompleteOption(fun)); + } + }); + } + + getThreatIntelfunctions() { + this.threatIntelOptions = []; + for (let threatName of this.sampleThreatIntels) { + this.threatIntelOptions.push(new AutocompleteOption(threatName)); + } + + } + + isSimpleFunction(configuredFunctions: string[]) { + for (let configuredFunction of configuredFunctions) { + if (this.transformFunctions.filter(stellarFunctionDescription => stellarFunctionDescription.name === configuredFunction).length === 0) { + return false; + } + } + return true; + } + + isConditionalRemoveTransform(fieldTransformer: FieldTransformer): boolean { + if (fieldTransformer && fieldTransformer.transformation === 'REMOVE' && + fieldTransformer.config && fieldTransformer.config['condition']) { + return true; + } + + return false; + } + + createFieldSchemaRows() { + this.fieldSchemaRows = []; + this.savedFieldSchemaRows = []; + let fieldSchemaRowsCreated = {}; + + // Update rows with Stellar transformations + let stellarTransformations = this.sensorParserConfig.fieldTransformations.filter(fieldTransformer => fieldTransformer.transformation === 'STELLAR'); + for (let fieldTransformer of stellarTransformations) { + if (fieldTransformer.config) { + for (let outputFieldName of Object.keys(fieldTransformer.config)) { + let stellarFunctionStatement = fieldTransformer.config[outputFieldName]; + let configuredFunctions = stellarFunctionStatement.split('('); + let inputFieldName = configuredFunctions.splice(-1, 1)[0].replace(new RegExp('\\)', 'g'), ''); + configuredFunctions.reverse(); + if (!fieldSchemaRowsCreated[inputFieldName]) { + fieldSchemaRowsCreated[inputFieldName] = new FieldSchemaRow(inputFieldName); + } + fieldSchemaRowsCreated[inputFieldName].outputFieldName = outputFieldName; + fieldSchemaRowsCreated[inputFieldName].preview = stellarFunctionStatement; + fieldSchemaRowsCreated[inputFieldName].isSimple = this.isSimpleFunction(configuredFunctions); + if (fieldSchemaRowsCreated[inputFieldName].isSimple) { + for (let configuredFunction of configuredFunctions) { + fieldSchemaRowsCreated[inputFieldName].transformConfigured.push(new AutocompleteOption(configuredFunction)); + } + } + } + } + } + + // Update rows with Remove Transformations + let removeTransformations = this.sensorParserConfig.fieldTransformations.filter(fieldTransformer => fieldTransformer.transformation === 'REMOVE'); + for (let fieldTransformer of removeTransformations) { + for (let inputFieldName of fieldTransformer.input) { + if (!fieldSchemaRowsCreated[inputFieldName]) { + fieldSchemaRowsCreated[inputFieldName] = new FieldSchemaRow(inputFieldName); + } + fieldSchemaRowsCreated[inputFieldName].isRemoved = true; + if (fieldTransformer.config && fieldTransformer.config['condition']) { + fieldSchemaRowsCreated[inputFieldName].conditionalRemove = true; + } + + } + } + + // Update rows with enrichments + if (this.sensorEnrichmentConfig.enrichment.fieldMap) { + for (let enrichment in this.sensorEnrichmentConfig.enrichment.fieldMap) { + if (enrichment !== 'hbaseEnrichment' && enrichment !== 'stellar') { + let fieldNames = this.sensorEnrichmentConfig.enrichment.fieldMap[enrichment]; + for (let fieldName of fieldNames) { + if (!fieldSchemaRowsCreated[fieldName]) { + fieldSchemaRowsCreated[fieldName] = new FieldSchemaRow(fieldName); + } + fieldSchemaRowsCreated[fieldName].enrichmentConfigured.push(new AutocompleteOption(enrichment)); + } + } + } + } + + // Update rows with HBase enrichments + if (this.sensorEnrichmentConfig.enrichment.fieldToTypeMap) { + for (let fieldName of Object.keys(this.sensorEnrichmentConfig.enrichment.fieldToTypeMap)) { + let enrichments = this.sensorEnrichmentConfig.enrichment.fieldToTypeMap[fieldName]; + if (!fieldSchemaRowsCreated[fieldName]) { + fieldSchemaRowsCreated[fieldName] = new FieldSchemaRow(fieldName); + } + for (let enrichment of enrichments) { + fieldSchemaRowsCreated[fieldName].enrichmentConfigured.push(new AutocompleteOption(enrichment)); + } + } + } + + // Update rows with threatIntels + if (this.sensorEnrichmentConfig.threatIntel.fieldToTypeMap) { + for (let fieldName of Object.keys(this.sensorEnrichmentConfig.threatIntel.fieldToTypeMap)) { + let threatIntels = this.sensorEnrichmentConfig.threatIntel.fieldToTypeMap[fieldName]; + + if (!fieldSchemaRowsCreated[fieldName]) { + fieldSchemaRowsCreated[fieldName] = new FieldSchemaRow(fieldName); + } + + for (let threatIntel of threatIntels) { + fieldSchemaRowsCreated[fieldName].threatIntelConfigured.push(new AutocompleteOption(threatIntel)); + } + } + } + + this.fieldSchemaRows = Object.keys(fieldSchemaRowsCreated).map(key => fieldSchemaRowsCreated[key]); + + // Adds rows from parseResult with no transformations/enrichments/threatIntels + let fieldSchemaRowsCreatedKeys = Object.keys(fieldSchemaRowsCreated); + for (let fieldName of Object.keys(this.parserResult).filter(fieldName => fieldSchemaRowsCreatedKeys.indexOf(fieldName) === -1)) { + let field = new FieldSchemaRow(fieldName); + field.isParserGenerated = true; + this.fieldSchemaRows.push(field); + } + + // save the initial fieldSchemaRows + for (let fieldSchemaRow of this.fieldSchemaRows) { + this.savedFieldSchemaRows.push(JSON.parse(JSON.stringify(fieldSchemaRow))); + } + } + + getChanges(fieldSchemaRow: FieldSchemaRow): string { + + if (fieldSchemaRow.isRemoved) { + return 'Disabled'; + } + + let transformFunction = fieldSchemaRow.transformConfigured.length > 0 ? this.createTransformFunction(fieldSchemaRow) : ''; + let enrichments = fieldSchemaRow.enrichmentConfigured.map(autocomplete => autocomplete.name).join(', '); + let threatIntel = fieldSchemaRow.threatIntelConfigured.map(autocomplete => autocomplete.name).join(', '); + + transformFunction = transformFunction.length > 30 ? (transformFunction.substring(0, 25) + '...') : transformFunction; + + let displayString = transformFunction.length > 0 ? ('Transforms: ' + transformFunction) : ''; + displayString += (transformFunction.length > 0 ? ' <br> ' : '') + (enrichments.length > 0 ? ('Enrichments: ' + enrichments) : ''); + displayString += (enrichments.length > 0 ? ' <br> ' : '') + (threatIntel.length > 0 ? ('Threat Intel: ' + threatIntel) : ''); + + return displayString; + } + + onSampleDataChanged(sampleData: string) { + let sensorTopicUpperCase = this.sensorParserConfig.sensorTopic.toUpperCase(); + let parseMessageRequest = new ParseMessageRequest(); + parseMessageRequest.sensorParserConfig = JSON.parse(JSON.stringify(this.sensorParserConfig)); + parseMessageRequest.grokStatement = this.grokStatement; + parseMessageRequest.sampleData = sampleData; + + if (parseMessageRequest.sensorParserConfig.parserConfig['patternLabel'] == null) { + parseMessageRequest.sensorParserConfig.parserConfig['patternLabel'] = sensorTopicUpperCase; + } + parseMessageRequest.sensorParserConfig.parserConfig['grokPath'] = './' + parseMessageRequest.sensorParserConfig.sensorTopic; + + this.sensorParserConfigService.parseMessage(parseMessageRequest).subscribe( + parserResult => { + this.parserResult = parserResult; + this.createFieldSchemaRows(); + }, + error => { + this.onSampleDataNotAvailable(); + }); + } + + onSampleDataNotAvailable() { + this.createFieldSchemaRows(); + } + + onDelete(fieldSchemaRow: FieldSchemaRow) { + this.fieldSchemaRows.splice(this.fieldSchemaRows.indexOf(fieldSchemaRow), 1); + this.savedFieldSchemaRows.splice(this.fieldSchemaRows.indexOf(fieldSchemaRow), 1); + } + + onRemove(fieldSchemaRow: FieldSchemaRow) { + fieldSchemaRow.isRemoved = true; + this.onSaveChange(fieldSchemaRow); + } + + onEnable(fieldSchemaRow: FieldSchemaRow) { + if (fieldSchemaRow.conditionalRemove) { + this.metronAlerts.showErrorMessage('The "' + fieldSchemaRow.outputFieldName + '" field cannot be enabled because the REMOVE transformation has a condition. Please remove the condition in the RAW JSON editor.'); + return; + } + fieldSchemaRow.isRemoved = false; + this.onSaveChange(fieldSchemaRow); + } + + onSaveChange(savedFieldSchemaRow: FieldSchemaRow) { + savedFieldSchemaRow.showConfig = false; + savedFieldSchemaRow.isNew = false; + let initialSchemaRow = this.savedFieldSchemaRows.filter(fieldSchemaRow => fieldSchemaRow.inputFieldName === savedFieldSchemaRow.inputFieldName)[0]; + Object.assign(initialSchemaRow, JSON.parse(JSON.stringify(savedFieldSchemaRow))); + + this.onSave(); + } + + onCancelChange(cancelledFieldSchemaRow: FieldSchemaRow) { + cancelledFieldSchemaRow.showConfig = false; + let initialSchemaRow = this.savedFieldSchemaRows.filter(fieldSchemaRow => fieldSchemaRow.inputFieldName === cancelledFieldSchemaRow.inputFieldName)[0]; + Object.assign(cancelledFieldSchemaRow, JSON.parse(JSON.stringify(initialSchemaRow))); + } + + onCancel(): void { + this.hideFieldSchema.emit(true); + } + + createTransformFunction(fieldSchemaRow: FieldSchemaRow): string { + let func = fieldSchemaRow.inputFieldName; + + for (let config of fieldSchemaRow.transformConfigured) { + func = config.name + '(' + func + ')'; + } + + return func; + } + + onTransformsChange(fieldSchemaRow: FieldSchemaRow): void { + fieldSchemaRow.preview = fieldSchemaRow.transformConfigured.length === 0 ? '' : this.createTransformFunction(fieldSchemaRow); + } + + addNewRule() { + let fieldSchemaRow = new FieldSchemaRow('new'); + fieldSchemaRow.isNew = true; + fieldSchemaRow.showConfig = true; + fieldSchemaRow.inputFieldName = ''; + this.fieldSchemaRows.push(fieldSchemaRow); + } + + onSave() { + let removeTransformations: string[] = []; + + // Remove all STELLAR functions and retain only the REMOVE objects + this.sensorParserConfig.fieldTransformations = this.sensorParserConfig.fieldTransformations.filter(fieldTransformer => { + if (this.isConditionalRemoveTransform(fieldTransformer)) { + return true; + } + return false; + }); + + let transformConfigObject = new FieldTransformer(); + transformConfigObject.output = []; + transformConfigObject.config = {}; + transformConfigObject.transformation = 'STELLAR'; + + let enrichmentConfigObject = new EnrichmentConfig(); + enrichmentConfigObject.config = {}; + let threatIntelConfigObject = new ThreatIntelConfig(); + threatIntelConfigObject.triageConfig = this.sensorEnrichmentConfig.threatIntel.triageConfig; + + + for (let fieldSchemaRow of this.savedFieldSchemaRows) { + if (fieldSchemaRow.transformConfigured.length > 0) { + transformConfigObject.output.push(fieldSchemaRow.outputFieldName); + transformConfigObject.config[fieldSchemaRow.outputFieldName] = this.createTransformFunction(fieldSchemaRow); + } + if (fieldSchemaRow.isRemoved && !fieldSchemaRow.conditionalRemove) { + removeTransformations.push(fieldSchemaRow.inputFieldName); + } + if (fieldSchemaRow.enrichmentConfigured.length > 0) { + for (let option of fieldSchemaRow.enrichmentConfigured) { + if (option.name === 'geo' || option.name === 'host') { + if (!enrichmentConfigObject.fieldMap[option.name]) { + enrichmentConfigObject.fieldMap[option.name] = []; + } + enrichmentConfigObject.fieldMap[option.name].push(fieldSchemaRow.inputFieldName); + } else { + if (!enrichmentConfigObject.fieldMap['hbaseEnrichment']) { + enrichmentConfigObject.fieldMap['hbaseEnrichment'] = []; + } + enrichmentConfigObject.fieldMap['hbaseEnrichment'].push(fieldSchemaRow.inputFieldName); + if (!enrichmentConfigObject.fieldToTypeMap[fieldSchemaRow.inputFieldName]) { + enrichmentConfigObject.fieldToTypeMap[fieldSchemaRow.inputFieldName] = []; + } + enrichmentConfigObject.fieldToTypeMap[fieldSchemaRow.inputFieldName].push(option.name); + } + } + } + if (fieldSchemaRow.threatIntelConfigured.length > 0) { + for (let option of fieldSchemaRow.threatIntelConfigured) { + if (!threatIntelConfigObject.fieldMap['hbaseThreatIntel']) { + threatIntelConfigObject.fieldMap['hbaseThreatIntel'] = []; + } + threatIntelConfigObject.fieldMap['hbaseThreatIntel'].push(fieldSchemaRow.inputFieldName); + if (!threatIntelConfigObject.fieldToTypeMap[fieldSchemaRow.inputFieldName]) { + threatIntelConfigObject.fieldToTypeMap[fieldSchemaRow.inputFieldName] = []; + } + threatIntelConfigObject.fieldToTypeMap[fieldSchemaRow.inputFieldName].push(option.name); + } + } + } + + if (Object.keys(transformConfigObject.config).length > 0) { + this.sensorParserConfig.fieldTransformations.push(transformConfigObject); + } + + if (removeTransformations.length > 0) { + let removeConfigObject = new FieldTransformer(); + removeConfigObject.transformation = 'REMOVE'; + removeConfigObject.input = removeTransformations; + this.sensorParserConfig.fieldTransformations.push(removeConfigObject); + } + + this.sensorEnrichmentConfig.enrichment = enrichmentConfigObject; + this.sensorEnrichmentConfig.threatIntel = threatIntelConfigObject; + } +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.module.ts new file mode 100644 index 0000000..afa3d55 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-field-schema/sensor-field-schema.module.ts @@ -0,0 +1,30 @@ +/** + * 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 {MultipleInputModule} from '../../shared/multiple-input/multiple-input.module'; +import {SampleDataModule} from '../../shared/sample-data/sample-data.module'; +import {SensorFieldSchemaComponent} from './sensor-field-schema.component'; + +@NgModule ({ + imports: [ SharedModule, MultipleInputModule, SampleDataModule ], + declarations: [ SensorFieldSchemaComponent ], + exports: [ SensorFieldSchemaComponent ] +}) + +export class SensorFieldSchemaModule { } http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-grok/index.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-grok/index.ts b/metron-interface/metron-config/src/app/sensors/sensor-grok/index.ts new file mode 100644 index 0000000..03c0507 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-grok/index.ts @@ -0,0 +1,18 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './sensor-grok.component'; http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.html b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.html new file mode 100644 index 0000000..c9bdc06 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.html @@ -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. + --> +<div class="metron-slider-pane-edit fill load-left-to-right dialog2x"> + + <div class="form-title">Grok Validator</div> + <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="onCancelGrok()"></i> + + <form role="form" class="grok-form"> + <metron-config-sample-data [topic]="sensorParserConfig.sensorTopic" + (onSampleDataChanged)="onSampleDataChanged($event)"></metron-config-sample-data> + <label attr.for="patternLabel">PATTERN LABEL</label> + <select class="form-control pattern-label-dropdown" [ngModelOptions]="{standalone: true}" [(ngModel)]="newPatternLabel"> + <option *ngFor="let patternLabel of availablePatternLabels" [value]="patternLabel"> {{ patternLabel }} </option> + </select> + <label attr.for="grokStatement">STATEMENT</label> + <metron-config-ace-editor [(ngModel)]="newGrokStatement" [ngModelOptions]="{standalone: true}" [type]="'GROK'" [options]="grokFunctionList" [placeHolder]="'Enter Grok statement'" (ngModelChange)="getAvailablePatternLabels()"> </metron-config-ace-editor> + + <div class="buttons-bar"> + <button type="submit" class="btn form-enable-disable-button" [disabled]="isTestDisabled()" (click)="onTestGrokStatement()">TEST</button> + <button type="submit" class="btn form-enable-disable-button" [disabled]="isSaveDisabled()" (click)="onSaveGrok()">SAVE</button> + </div> + + <label> PREVIEW </label> + <table class="table form-table" #table> + <tbody> + <tr *ngFor="let key of parsedMessageKeys"> + <td>{{ key }}</td> + <td>{{ parsedMessage[key] }}</td> + </tr> + </tbody> + </table> + </form> +</div> http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.scss new file mode 100644 index 0000000..924d6a4 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.scss @@ -0,0 +1,67 @@ +/** + * 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-title +{ + padding-left: 25px; +} + +.grok-form +{ + padding-left: 25px; + padding-right: 20px; +} + +.close-button +{ + padding-right: 20px; +} + + +.form-table +{ + display: inline-block; + min-height: 100px; + background: #033339; + margin-bottom: 100px; +} +.table, table +{ + margin-top: 0px; + tr td + { + border-bottom: 1px solid #3F4748; + } + tr:last-child td + { + border-bottom: none; + } + td:last-child { + width: 100%; + } + +} +.buttons-bar +{ + margin-top: 10px; + margin-bottom: 15px; +} + +.pattern-label-dropdown +{ + width: 40%; +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.spec.ts new file mode 100644 index 0000000..eac839e --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.spec.ts @@ -0,0 +1,234 @@ +/** + * 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 { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import {SimpleChange} from '@angular/core'; +import {Http} from '@angular/http'; +import {SensorParserConfigService} from '../../service/sensor-parser-config.service'; +import {MetronAlerts} from '../../shared/metron-alerts'; +import {KafkaService} from '../../service/kafka.service'; +import {Observable} from 'rxjs/Observable'; +import {ParseMessageRequest} from '../../model/parse-message-request'; +import {SensorGrokComponent} from './sensor-grok.component'; +import {GrokValidationService} from '../../service/grok-validation.service'; +import {SensorGrokModule} from './sensor-grok.module'; +import {SensorParserConfig} from '../../model/sensor-parser-config'; +import '../../rxjs-operators'; + +class MockSensorParserConfigService { + + private parsedMessage: string; + + public parseMessage(parseMessageRequest: ParseMessageRequest): Observable<{}> { + if (this.parsedMessage === 'ERROR') { + return Observable.throw({'_body': JSON.stringify({'abc': 'def'}) }); + } + + return Observable.create(observer => { + observer.next(this.parsedMessage); + observer.complete(); + }); + } + + public setParsedMessage(parsedMessage: any) { + this.parsedMessage = parsedMessage; + } +} + +class MockGrokValidationService { + public list(): Observable<string[]> { + return Observable.create(observer => { + observer.next({ + 'BASE10NUM': '(?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+)))', + 'BASE16FLOAT': '\\b(?<![0-9A-Fa-f.])(?:[+-]?(?:0x)?(?:(?:[0-9A-Fa-f]+(?:\\.[0-9A-Fa-f]*)?)|(?:\\.[0-9A-Fa-f]+)))\\b', + 'BASE16NUM': '(?<![0-9A-Fa-f])(?:[+-]?(?:0x)?(?:[0-9A-Fa-f]+))', + 'CISCOMAC': '(?:(?:[A-Fa-f0-9]{4}\\.){2}[A-Fa-f0-9]{4})', + 'COMMONMAC': '(?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})', + 'DATA': '.*?' + }); + observer.complete(); + }); + } +} + +class MockKafkaService { + +} + +describe('Component: SensorGrok', () => { + let component: SensorGrokComponent; + let grokValidationService: GrokValidationService; + let fixture: ComponentFixture<SensorGrokComponent>; + let sensorParserConfigService: MockSensorParserConfigService; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [SensorGrokModule], + providers: [ + MetronAlerts, + {provide: Http}, + {provide: KafkaService, useClass: MockKafkaService}, + {provide: SensorParserConfigService, useClass: MockSensorParserConfigService}, + {provide: GrokValidationService, useClass: MockGrokValidationService}, + + ] + }).compileComponents() + .then(() => { + fixture = TestBed.createComponent(SensorGrokComponent); + component = fixture.componentInstance; + sensorParserConfigService = fixture.debugElement.injector.get(SensorParserConfigService); + grokValidationService = fixture.debugElement.injector.get(GrokValidationService); + }); + })); + + it('should create an instance', () => { + expect(component).toBeDefined(); + fixture.destroy(); + }); + + it('should handle ngOnInit', async(() => { + component.ngOnInit(); + + expect(Object.keys(component.grokFunctionList).length).toEqual(6); + + fixture.destroy(); + })); + + it('should handle ngOnChanges', async(() => { + spyOn(component.sampleData, 'getNextSample'); + + let changes = { + 'showGrok': new SimpleChange(true, false) + }; + component.ngOnChanges(changes); + expect(component.sampleData.getNextSample['calls'].count()).toEqual(0); + + changes = { + 'showGrok': new SimpleChange(false, true) + }; + + component.grokStatement = 'STATEMENT_1 grok statement 1\nSTATEMENT_2 grok statement 2\n'; + component.patternLabel = 'STATEMENT_2'; + component.ngOnChanges(changes); + expect(component.newGrokStatement).toEqual('STATEMENT_1 grok statement 1\nSTATEMENT_2 grok statement 2\n'); + expect(component.newPatternLabel).toEqual('STATEMENT_2'); + expect(component.availablePatternLabels).toEqual(['STATEMENT_1', 'STATEMENT_2']); + + component.grokStatement = ''; + component.patternLabel = 'PATTERN_LABEL'; + component.ngOnChanges(changes); + expect(component.newGrokStatement).toEqual('PATTERN_LABEL '); + expect(component.newPatternLabel).toEqual('PATTERN_LABEL'); + expect(component.availablePatternLabels).toEqual(['PATTERN_LABEL']); + + expect(component.sampleData.getNextSample['calls'].count()).toEqual(2); + + fixture.destroy(); + })); + + it('should test grok statement validation', async(() => { + + let parsedMessage = { + 'action': 'TCP_MISS', + 'bytes': 337891, + 'code': 200, + 'elapsed': 415, + 'ip_dst_addr': '207.109.73.154', + 'ip_src_addr': '127.0.0.1', + 'method': 'GET', + 'timestamp': '1467011157.401', + 'url': 'http://www.aliexpress.com/af/shoes.html?' + }; + sensorParserConfigService.setParsedMessage(parsedMessage); + + let sampleData = '1467011157.401 415 127.0.0.1 TCP_MISS/200 337891 GET http://www.aliexpress.com/af/shoes.html? ' + + '- DIRECT/207.109.73.154 text/html'; + let grokStatement = 'SQUID_DELIMITED %{NUMBER:timestamp} %{INT:elapsed} %{IPV4:ip_src_addr} %{WORD:action}/%{NUMBER:code} ' + + '%{NUMBER:bytes} %{WORD:method} %{NOTSPACE:url} - %{WORD:UNWANTED}\/%{IPV4:ip_dst_addr} %{WORD:UNWANTED}\/%{WORD:UNWANTED}'; + + component.sensorParserConfig = new SensorParserConfig(); + component.sensorParserConfig.sensorTopic = 'squid'; + component.newGrokStatement = grokStatement; + + component.onSampleDataChanged(''); + expect(component.parsedMessage).toEqual({}); + expect(component.parsedMessageKeys).toEqual([]); + + component.onSampleDataChanged(sampleData); + expect(component.parsedMessage).toEqual(parsedMessage); + expect(component.parsedMessageKeys).toEqual(['action', 'bytes', 'code', 'elapsed', 'ip_dst_addr', + 'ip_src_addr', 'method', 'timestamp', 'url']); + + sensorParserConfigService.setParsedMessage('ERROR'); + component.onTestGrokStatement(); + + expect(component.parsedMessage).toEqual({}); + + component.newGrokStatement = ''; + component.onTestGrokStatement(); + expect(component.parsedMessage).toEqual({}); + + fixture.destroy(); + })); + + it('should call appropriate functions on save ', () => { + spyOn(component.hideGrok, 'emit'); + spyOn(component.onSaveGrokStatement, 'emit'); + spyOn(component.onSavePatternLabel, 'emit'); + component.newGrokStatement = 'grok statement'; + component.newPatternLabel = 'PATTERN_LABEL'; + + component.onSaveGrok(); + + expect(component.onSaveGrokStatement.emit).toHaveBeenCalledWith('grok statement'); + expect(component.onSavePatternLabel.emit).toHaveBeenCalledWith('PATTERN_LABEL'); + expect(component.hideGrok.emit).toHaveBeenCalled(); + fixture.destroy(); + }); + + it('should call appropriate functions on cancel ', () => { + spyOn(component.hideGrok, 'emit'); + spyOn(component.onSaveGrokStatement, 'emit'); + spyOn(component.onSavePatternLabel, 'emit'); + + component.onCancelGrok(); + + expect(component.onSaveGrokStatement.emit).not.toHaveBeenCalled(); + expect(component.onSavePatternLabel.emit).not.toHaveBeenCalled(); + expect(component.hideGrok.emit).toHaveBeenCalled(); + fixture.destroy(); + }); + + it('should disable test', () => { + expect(component.isTestDisabled()).toEqual(true); + component.newGrokStatement = 'new grok statement'; + expect(component.isTestDisabled()).toEqual(true); + component.parseMessageRequest.sampleData = 'sample data'; + expect(component.isTestDisabled()).toEqual(false); + }); + + it('should disable save', () => { + component.availablePatternLabels = ['LABEL_1', 'LABEL_2']; + expect(component.isSaveDisabled()).toEqual(true); + component.newGrokStatement = 'new grok statement'; + expect(component.isSaveDisabled()).toEqual(true); + component.newPatternLabel = 'LABEL_2'; + expect(component.isSaveDisabled()).toEqual(false); + }); + +}); http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.ts new file mode 100644 index 0000000..c8bf513 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.component.ts @@ -0,0 +1,135 @@ +import { Component, OnInit, Input, OnChanges, SimpleChanges, ViewChild, EventEmitter, Output} from '@angular/core'; +import {SensorParserConfig} from '../../model/sensor-parser-config'; +import {ParseMessageRequest} from '../../model/parse-message-request'; +import {SensorParserConfigService} from '../../service/sensor-parser-config.service'; +import {AutocompleteOption} from '../../model/autocomplete-option'; +import {GrokValidationService} from '../../service/grok-validation.service'; +import {SampleDataComponent} from '../../shared/sample-data/sample-data.component'; +import {MetronAlerts} from '../../shared/metron-alerts'; + +@Component({ + selector: 'metron-config-sensor-grok', + templateUrl: './sensor-grok.component.html', + styleUrls: ['./sensor-grok.component.scss'] +}) +export class SensorGrokComponent implements OnInit, OnChanges { + + @Input() showGrok; boolean; + @Input() sensorParserConfig: SensorParserConfig; + @Input() grokStatement: string; + @Input() patternLabel: string; + + @Output() hideGrok = new EventEmitter<void>(); + @Output() onSaveGrokStatement = new EventEmitter<string>(); + @Output() onSavePatternLabel = new EventEmitter<string>(); + + @ViewChild(SampleDataComponent) sampleData: SampleDataComponent; + + newGrokStatement = ''; + newPatternLabel = ''; + availablePatternLabels = []; + parsedMessage: any = {}; + parsedMessageKeys: string[] = []; + grokFunctionList: AutocompleteOption[] = []; + parseMessageRequest: ParseMessageRequest = new ParseMessageRequest(); + + constructor(private sensorParserConfigService: SensorParserConfigService, private grokValidationService: GrokValidationService, + private metronAlerts: MetronAlerts) { + this.parseMessageRequest.sampleData = ''; + } + + ngOnInit() { + this.getGrokFunctions(); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes['showGrok'] && changes['showGrok'].currentValue) { + this.newPatternLabel = this.patternLabel; + if (this.grokStatement) { + this.newGrokStatement = this.grokStatement; + } else { + this.newGrokStatement = this.newPatternLabel + ' '; + } + this.getAvailablePatternLabels(); + this.sampleData.getNextSample(); + } + } + + onSampleDataChanged(sampleData: string) { + if (sampleData) { + this.parseMessageRequest.sampleData = sampleData; + this.onTestGrokStatement(); + } + } + + onTestGrokStatement() { + this.parsedMessage = {}; + + if (this.newGrokStatement.indexOf('%{') === -1) { + return; + } + + this.parseMessageRequest.sensorParserConfig = JSON.parse(JSON.stringify(this.sensorParserConfig)); + this.parseMessageRequest.grokStatement = this.newGrokStatement; + this.parseMessageRequest.sensorParserConfig.parserConfig['patternLabel'] = this.newPatternLabel; + this.parseMessageRequest.sensorParserConfig.parserConfig['grokPath'] = './' + this.parseMessageRequest.sensorParserConfig.sensorTopic; + + this.sensorParserConfigService.parseMessage(this.parseMessageRequest).subscribe( + result => { + this.parsedMessage = result; + this.setParsedMessageKeys(); + }, error => { + this.metronAlerts.showErrorMessage(error.message); + this.setParsedMessageKeys(); + }); + } + + private getGrokFunctions() { + this.grokValidationService.list().subscribe(result => { + Object.keys(result).forEach(name => { + let autocompleteOption: AutocompleteOption = new AutocompleteOption(); + autocompleteOption.name = name; + this.grokFunctionList.push(autocompleteOption); + }); + }); + } + + private setParsedMessageKeys() { + try { + this.parsedMessageKeys = Object.keys(this.parsedMessage).sort(); + } catch (e) { + this.parsedMessageKeys = []; + } + } + + onSaveGrok(): void { + this.onSaveGrokStatement.emit(this.newGrokStatement); + this.onSavePatternLabel.emit(this.newPatternLabel); + this.hideGrok.emit(); + } + + onCancelGrok(): void { + this.hideGrok.emit(); + } + + private getAvailablePatternLabels() { + this.availablePatternLabels = []; + let statements = this.newGrokStatement.split('\n'); + for (let statement of statements) { + if (statement) { + let patternLabel = statement.split(' ')[0]; + this.availablePatternLabels.push(patternLabel); + } + } + } + + isTestDisabled() { + return this.parseMessageRequest.sampleData.length === 0 || this.newGrokStatement.length === 0; + } + + isSaveDisabled() { + return this.newGrokStatement.length === 0 || this.availablePatternLabels.indexOf(this.newPatternLabel) === -1; + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.module.ts new file mode 100644 index 0000000..ef31798 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-grok/sensor-grok.module.ts @@ -0,0 +1,30 @@ +/** + * 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 {SensorGrokComponent} from './sensor-grok.component'; +import {AceEditorModule} from '../../shared/ace-editor/ace-editor.module'; +import {SampleDataModule} from '../../shared/sample-data/sample-data.module'; + +@NgModule ({ + imports: [ SharedModule, AceEditorModule, SampleDataModule ], + declarations: [ SensorGrokComponent ], + exports: [ SensorGrokComponent ] +}) + +export class SensorGrokModule { } http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/index.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/index.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/index.ts new file mode 100644 index 0000000..3a63c39 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/index.ts @@ -0,0 +1 @@ +export * from './sensor-parser-config-readonly.component'; http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.html b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.html new file mode 100644 index 0000000..d988dd1 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.html @@ -0,0 +1,108 @@ +<!-- + Licensed to the Apache Software + Foundation (ASF) under one or more contributor license agreements. See the + NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to You under the Apache License, + Version 2.0 (the "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed + under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + OR CONDITIONS OF ANY KIND, either express or implied. See the License for + the specific language governing permissions and limitations under the License. + --> +<metron-config-metron-modal [backgroundMasked]="false"> + + <div class="metron-slider-pane fill load-right-to-left dialog1x"> + <div class="metron-readonly-pane"> + <div class="row"> + <div class="col-xs-12 form-title">{{selectedSensorName}} + <i class="fa fa-times pull-right close-button" aria-hidden="true" (click)="goBack()"></i> + </div> + </div> + + <div *ngFor="let item of editViewMetaData"> + <div [ngSwitch]="item.type"> + + <div *ngSwitchCase="'SEPARATOR'" class="row form-seperator"> + </div> + + <div *ngSwitchCase="'SPACER'" class="row"> + <div class="col-xs-12"> </div> + </div> + + <div *ngSwitchCase="'TITLE'" class="row"> + <div class="col-xs-12 form-sub-title">{{ item.value }}</div> + </div> + + <div *ngSwitchDefault class="row"> + <div *ngIf="item.label!=''" class="col-xs-6 form-label" [ngClass]="{'form-value font-weight-bold': item.boldTitle}">{{ item.label }}</div> + <div *ngIf="item.model == 'sensorParserConfigHistory'" class="col-xs-6 px-0 pull-left form-value">{{ sensorParserConfigHistory[item.value] ? sensorParserConfigHistory[item.value] : "-" }}</div> + <div *ngIf="item.model == 'kafkaTopic'" class="col-xs-6 px-0 pull-left form-value">{{ kafkaTopic[item.value] ? kafkaTopic[item.value] : "-" }}</div> + <div *ngIf="item.model == 'topologyStatus'" class="col-xs-6 px-0 pull-left form-value">{{ getTopologyStatus(item.value) }}</div> + + <div *ngIf="item.model == 'grokStatement' && sensorParserConfigHistory.config.parserClassName === 'org.apache.metron.parsers.GrokParser'" style="border: none"> + <div class="col-xs-12 form-sub-title">Grok Statement</div> + <div id="collapseGrok" class="col-xs-12 pull-left form-value panel-collapse collapse"></div> + <div class="col-xs-12 pull-left form-value grok" [innerHtml]="grokStatement"></div> + <a *ngIf="grokStatement.length>0" class="collapsed blue-label font-weight-bold col-xs-8 col-xs-push-4" data-toggle="collapse" href="#collapseGrok" aria-expanded="false" aria-controls="collapseGrok" #grokLink (click)="grokLink.text=(grokLink.text==='show more')?'show less':'show more'">show more</a> + <div class="px-1"> <div class="col-xs-12 form-seperator"></div> </div> + </div> + + <div *ngIf="item.model == 'transforms'"> + + <div id="collapseTransform" class="col-xs-12 pull-left form-value panel-collapse collapse"> + <div class="form-sub-sub-title">Transforms</div> + <div> + <div *ngFor="let results of transformsConfigKeys" > + <div class="form-label">{{ results }}</div> + <div class="form-value">{{ transformsConfigMap[results] }}</div> + </div> + </div> + </div> + + <div class="transforms"> + <div class="col-xs-12 form-sub-sub-title">Transforms</div> + <div class="col-xs-12 form-label " [innerHtml]="getTransformsOutput()"></div> + </div> + <a *ngIf="transformsConfigKeys.length>0" class="collapsed blue-label font-weight-bold col-xs-8 col-xs-push-4" data-toggle="collapse" href="#collapseTransform" aria-expanded="false" aria-controls="collapseTransform" (click)="toggleTransformLink()">{{transformLinkText}}</a> + </div> + <div *ngIf="item.model == 'threatTriageRules'" class="threat-triage-rules"> + <div class="col-xs-6 form-label">AGGREGATOR</div><div class="col-xs-6 form-value">{{sensorEnrichmentConfig.threatIntel.triageConfig.aggregator}}</div> + <div id="collapseThreatTriage" class="col-xs-12 pull-left form-value panel-collapse collapse"> + <div> + <div class="col-xs-6 form-sub-sub-title">NAME</div><div class="col-xs-6 form-sub-sub-title">SCORE</div> + <div *ngFor="let riskLevelRule of rules" > + <div class="col-xs-6 form-label">{{ getDisplayName(riskLevelRule) }}</div> + <div class="col-xs-6 form-value">{{ riskLevelRule.score }}</div> + </div> + </div> + </div> + <div class="col-xs-12 form-label threatIntel">{{ getRuleDisplayName() }}</div> + <a *ngIf="rules.length>0" class="collapsed blue-label font-weight-bold col-xs-8 col-xs-push-4" data-toggle="collapse" href="#collapseThreatTriage" aria-expanded="false" aria-controls="collapseThreatTriage" (click)="toggleThreatTriageLink()">{{threatTriageLinkText}}</a> + </div> + </div> + </div> + </div> + </div> + + <div class="metron-button-bar" > + <div class="row pl-0 py-1"> + <button class="btn btn-primary" type="button" (click)="onEditSensor()" [disabled]="startStopInProgress">EDIT</button> + + <button class="btn btn-primary" type="button" (click)="onStartSensor()" [disabled]="startStopInProgress" [hidden]="isStartHidden()">START</button> + <button class="btn form-enable-disable-button" type="button" (click)="onStopSensor()" [disabled]="startStopInProgress" [hidden]="isStopHidden()">STOP</button> + + <button class="btn btn-primary" type="button" (click)="onEnableSensor()" [disabled]="startStopInProgress" [hidden]="isEnableHidden()" >ENABLE</button> + <button class="btn form-enable-disable-button" type="button" (click)="onDisableSensor()" [disabled]="startStopInProgress" [hidden]="isDisableHidden()">DISABLE</button> + + <button class="btn btn-link delete" type="button" (click)="onDeleteSensor()"> Delete </button> + + <div class="start-stop-progress"> <i class="fa fa-circle-o-notch fa-spin fa-lg fa-fw" [hidden]="!startStopInProgress"></i> </div> + </div> + </div> + </div> + +</metron-config-metron-modal> http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.scss new file mode 100644 index 0000000..b78b559 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.scss @@ -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 "../../_variables.scss"; +@import "../../../styles.scss"; + +.metron-readonly-pane { + margin-bottom: $button-bar-height + 10px; +} + +.metron-button-bar +{ + background: $gray-light; + border-top: solid 2px $gray-border; + border-left: solid 1px $gray-border; + position: fixed; + width: inherit; +} + +.grok +{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: Roboto-Regular; + font-size: 13px; + color: $text-color-white +} + +.in + .grok +{ + font-size: 13px; + overflow: auto; + display: inline-block; + white-space: normal; + font-family: Roboto-Regular; + color: $text-color-white; +} + +.threatIntel +{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.in ~ .transforms, .in + .threatIntel +{ + display: none; +} + +a +{ + width: 100%; + text-align: center; + font-family: Roboto-Regular; +} + +.form-enable-disable-button +{ + min-width: 50px; +} + +.delete +{ + color:#E45D55; + font-weight: bold; + padding: 0.1rem; +} + +.form-sub-sub-title +{ + font-size: 14px; + font-family: Roboto-Regular; + color: $text-color-white; +} + +.form-sub-title +{ + font-size: 16px; + font-family: Roboto-Regular; + color: $form-field-text-color; +} + +.start-stop-progress +{ + position: absolute; + top: 20px; + right: 140px; +} + +.threat-triage-rules { + .form-label { + word-wrap: initial; + } +}