http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts new file mode 100644 index 0000000..dbbec12 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.spec.ts @@ -0,0 +1,708 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {Observable} from 'rxjs/Observable'; +import {Router, ActivatedRoute, Params} from '@angular/router'; +import {Inject} from '@angular/core'; +import {SensorParserConfigHistory} from '../../model/sensor-parser-config-history'; +import {RequestOptions, Response, ResponseOptions, Http} from '@angular/http'; +import {SensorParserConfigReadonlyComponent} from './sensor-parser-config-readonly.component'; +import {SensorParserConfigService} from '../../service/sensor-parser-config.service'; +import {KafkaService} from '../../service/kafka.service'; +import {TopologyStatus} from '../../model/topology-status'; +import {SensorParserConfig} from '../../model/sensor-parser-config'; +import {KafkaTopic} from '../../model/kafka-topic'; +import {AuthenticationService} from '../../service/authentication.service'; +import {SensorParserConfigHistoryService} from '../../service/sensor-parser-config-history.service'; +import {StormService} from '../../service/storm.service'; +import {MetronAlerts} from '../../shared/metron-alerts'; +import {FieldTransformer} from '../../model/field-transformer'; +import {SensorParserConfigReadonlyModule} from './sensor-parser-config-readonly.module'; +import {APP_CONFIG, METRON_REST_CONFIG} from '../../app.config'; +import {IAppConfig} from '../../app.config.interface'; +import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service'; +import {SensorEnrichmentConfig, EnrichmentConfig, ThreatIntelConfig} from '../../model/sensor-enrichment-config'; +import {HdfsService} from '../../service/hdfs.service'; +import {GrokValidationService} from '../../service/grok-validation.service'; + +class MockRouter { + + navigateByUrl(url: string) { + + } + +} + +class MockActivatedRoute { + private name: string; + params: Observable<Params>; + + setNameForTest(name: string) { + this.name = name; + this.params = Observable.create(observer => { + observer.next({id: this.name}); + observer.complete(); + }); + } +} + +class MockAuthenticationService extends AuthenticationService { + + constructor(private http2: Http, private router2: Router, @Inject(APP_CONFIG) private config2: IAppConfig) { + super(http2, router2, config2); + } + + public getCurrentUser(options: RequestOptions): Observable<Response> { + let responseOptions: ResponseOptions = new ResponseOptions(); + responseOptions.body = 'user'; + let response: Response = new Response(responseOptions); + return Observable.create(observer => { + observer.next(response); + observer.complete(); + }); + }; +} + +class MockSensorParserConfigHistoryService extends SensorParserConfigHistoryService { + + private sensorParserConfigHistory: SensorParserConfigHistory; + + constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) { + super(http2, config2); + } + + public setForTest(sensorParserConfigHistory: SensorParserConfigHistory) { + this.sensorParserConfigHistory = sensorParserConfigHistory; + } + + public get(name: string): Observable<SensorParserConfigHistory> { + return Observable.create(observer => { + observer.next(this.sensorParserConfigHistory); + observer.complete(); + }); + } +} + +class MockSensorParserConfigService extends SensorParserConfigService { + + constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) { + super(http2, config2); + } + +} + +class MockStormService extends StormService { + private topologyStatus: TopologyStatus; + + constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) { + super(http2, config2); + } + + public setForTest(topologyStatus: TopologyStatus) { + this.topologyStatus = topologyStatus; + } + + public getStatus(name: string): Observable<TopologyStatus> { + return Observable.create(observer => { + observer.next(this.topologyStatus); + observer.complete(); + }); + } +} + +class MockGrokValidationService extends GrokValidationService { + + constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) { + super(http2, config2); + } + + public list(): Observable<string[]> { + return Observable.create(observer => { + observer.next({ + 'BASE10NUM': '(?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+)))', + 'BASE16FLOAT': '\\b(?<![0-9A-Fa-f.])(?:[+-]?(?:0x)?(?:(?:[0-9A-Fa-f]+(?:\\.[0-9A-Fa-f]*)?)|(?:\\.[0-9A-Fa-f]+)))\\b', + 'BASE16NUM': '(?<![0-9A-Fa-f])(?:[+-]?(?:0x)?(?:[0-9A-Fa-f]+))', + 'CISCOMAC': '(?:(?:[A-Fa-f0-9]{4}\\.){2}[A-Fa-f0-9]{4})', + 'COMMONMAC': '(?:(?:[A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2})', + 'DATA': '.*?' + }); + observer.complete(); + }); + } +} + +class MockKafkaService extends KafkaService { + + private kafkaTopic: KafkaTopic; + + constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) { + super(http2, config2); + } + + public setForTest(kafkaTopic: KafkaTopic) { + this.kafkaTopic = kafkaTopic; + } + + public get(name: string): Observable<KafkaTopic> { + return Observable.create(observer => { + observer.next(this.kafkaTopic); + observer.complete(); + }); + } + + public sample(name: string): Observable<string> { + return Observable.create(observer => { + observer.next(JSON.stringify({'data': 'data1', 'data2': 'data3'})); + observer.complete(); + }); + } +} + +class MockHdfsService extends HdfsService { + private fileList: string[]; + private contents: string; + + constructor(private http2: Http, @Inject(APP_CONFIG) private config2: IAppConfig) { + super(http2, config2); + } + + public setContents(contents: string) { + this.contents = contents; + } + + public list(path: string): Observable<string[]> { + if (this.fileList === null) { + return Observable.throw('Error'); + } + return Observable.create(observer => { + observer.next(this.fileList); + observer.complete(); + }); + } + + public read(path: string): Observable<string> { + if (this.contents === null) { + return Observable.throw('Error'); + } + return Observable.create(observer => { + observer.next(this.contents); + observer.complete(); + }); + } + + public post(path: string, contents: string): Observable<Response> { + return Observable.create(observer => { + observer.next({}); + observer.complete(); + }); + } + + public deleteFile(path: string): Observable<Response> { + return Observable.create(observer => { + observer.next({}); + observer.complete(); + }); + } +} + +class MockSensorEnrichmentConfigService { + private sensorEnrichmentConfig: SensorEnrichmentConfig; + + setForTest(sensorEnrichmentConfig: SensorEnrichmentConfig) { + this.sensorEnrichmentConfig = sensorEnrichmentConfig; + } + + public get(name: string): Observable<SensorEnrichmentConfig> { + return Observable.create(observer => { + observer.next(this.sensorEnrichmentConfig); + observer.complete(); + }); + } + + public getAvailable(): Observable<string[]> { + return Observable.create((observer) => { + observer.next(['geo', 'host', 'whois']); + observer.complete(); + }); + } +} + +describe('Component: SensorParserConfigReadonly', () => { + + let component: SensorParserConfigReadonlyComponent; + let fixture: ComponentFixture<SensorParserConfigReadonlyComponent>; + let sensorParserConfigHistoryService: MockSensorParserConfigHistoryService; + let sensorEnrichmentConfigService: MockSensorEnrichmentConfigService; + let sensorParserConfigService: SensorParserConfigService; + let kafkaService: MockKafkaService; + let hdfsService: MockHdfsService; + let grokValidationService: MockGrokValidationService; + let stormService: MockStormService; + let alerts: MetronAlerts; + let authenticationService: AuthenticationService; + let router: MockRouter; + let activatedRoute: MockActivatedRoute; + + beforeEach(async(() => { + + TestBed.configureTestingModule({ + imports: [SensorParserConfigReadonlyModule], + providers: [ + {provide: Http}, + {provide: ActivatedRoute, useClass: MockActivatedRoute}, + {provide: AuthenticationService, useClass: MockAuthenticationService}, + {provide: SensorEnrichmentConfigService, useClass: MockSensorEnrichmentConfigService}, + {provide: SensorParserConfigHistoryService, useClass: MockSensorParserConfigHistoryService}, + {provide: SensorParserConfigService, useClass: MockSensorParserConfigService}, + {provide: StormService, useClass: MockStormService}, + {provide: KafkaService, useClass: MockKafkaService}, + {provide: HdfsService, useClass: MockHdfsService}, + {provide: GrokValidationService, useClass: MockGrokValidationService}, + {provide: Router, useClass: MockRouter}, + {provide: APP_CONFIG, useValue: METRON_REST_CONFIG}, + MetronAlerts + ] + }).compileComponents() + .then(() => { + fixture = TestBed.createComponent(SensorParserConfigReadonlyComponent); + component = fixture.componentInstance; + activatedRoute = fixture.debugElement.injector.get(ActivatedRoute); + hdfsService = fixture.debugElement.injector.get(HdfsService); + authenticationService = fixture.debugElement.injector.get(AuthenticationService); + sensorParserConfigHistoryService = fixture.debugElement.injector.get(SensorParserConfigHistoryService); + sensorEnrichmentConfigService = fixture.debugElement.injector.get(SensorEnrichmentConfigService); + sensorParserConfigService = fixture.debugElement.injector.get(SensorParserConfigService); + stormService = fixture.debugElement.injector.get(StormService); + kafkaService = fixture.debugElement.injector.get(KafkaService); + grokValidationService = fixture.debugElement.injector.get(GrokValidationService); + router = fixture.debugElement.injector.get(Router); + alerts = fixture.debugElement.injector.get(MetronAlerts); + }); + + })); + + it('should create an instance', async(() => { + expect(component).toBeDefined(); + })); + + it('should have metadata defined ', async(() => { + expect(component.editViewMetaData.length).toEqual(24); + })); + + it('should have sensorsService with parserName and grokPattern defined and kafkaService defined', async(() => { + let sensorParserInfo = new SensorParserConfigHistory(); + let sensorParserConfig = new SensorParserConfig(); + let kafkaTopic = new KafkaTopic(); + let topologyStatus = new TopologyStatus(); + + sensorParserConfig.sensorTopic = 'bro'; + sensorParserConfig.parserClassName = 'org.apache.metron.parsers.GrokParser'; + sensorParserConfig.parserConfig = {grokPattern: 'SQUID_DELIMITED squid grok statement'}; + sensorParserInfo.config = sensorParserConfig; + + kafkaTopic.name = 'bro'; + kafkaTopic.numPartitions = 1; + kafkaTopic.replicationFactor = 1; + + topologyStatus.name = 'bro'; + topologyStatus.latency = 10.1; + topologyStatus.throughput = 15.2; + + let broEnrichment = { + 'fieldMap': { + 'geo': ['ip_dst_addr'], + 'host': ['ip_dst_addr'], + 'whois': [], + 'stellar': {'config': {'group1': {}}} + }, + 'fieldToTypeMap': {}, 'config': {} + }; + let broThreatIntel = {'threatIntel': { + 'fieldMap': { 'hbaseThreatIntel': ['ip_dst_addr'] }, + 'fieldToTypeMap': { 'ip_dst_addr': ['malicious_ip'] } + } + }; + let broEnrichments = new SensorEnrichmentConfig(); + broEnrichments.enrichment = Object.assign(new EnrichmentConfig(), broEnrichment); + broEnrichments.threatIntel = Object.assign(new ThreatIntelConfig(), broThreatIntel); + + sensorEnrichmentConfigService.setForTest(broEnrichments); + sensorParserConfigHistoryService.setForTest(sensorParserInfo); + kafkaService.setForTest(kafkaTopic); + stormService.setForTest(topologyStatus); + + activatedRoute.setNameForTest('bro'); + + component.ngOnInit(); + expect(component.startStopInProgress).toEqual(false); + expect(component.sensorParserConfigHistory).toEqual(Object.assign(new SensorParserConfigHistory(), sensorParserInfo)); + expect(component.kafkaTopic).toEqual(kafkaTopic); + expect(component.sensorEnrichmentConfig).toEqual(broEnrichments); + })); + + it('getSensorStatusService should initialise the state variable to appropriate values ', async(() => { + let sensorParserStatus = new TopologyStatus(); + sensorParserStatus.name = 'bro'; + sensorParserStatus.latency = 10.1; + sensorParserStatus.status = null; + sensorParserStatus.throughput = 15.2; + + stormService.setForTest(sensorParserStatus); + + component.getSensorStatusService(); + expect(component.getTopologyStatus('status')).toEqual('Stopped'); + expect(component.getTopologyStatus('sensorStatus')).toEqual('-'); + + sensorParserStatus.status = 'ACTIVE'; + component.getSensorStatusService(); + stormService.setForTest(sensorParserStatus); + expect(component.getTopologyStatus('status')).toEqual('Running'); + expect(component.getTopologyStatus('sensorStatus')).toEqual('Enabled'); + + sensorParserStatus.status = 'KILLED'; + component.getSensorStatusService(); + stormService.setForTest(sensorParserStatus); + expect(component.getTopologyStatus('status')).toEqual('Stopped'); + expect(component.getTopologyStatus('sensorStatus')).toEqual('-'); + + sensorParserStatus.status = 'INACTIVE'; + component.getSensorStatusService(); + stormService.setForTest(sensorParserStatus); + expect(component.getTopologyStatus('status')).toEqual('Disabled'); + expect(component.getTopologyStatus('sensorStatus')).toEqual('Disabled'); + })); + + it('setGrokStatement should set the variables appropriately ', async(() => { + let grokStatement = 'SQUID_DELIMITED squid grok statement'; + hdfsService.setContents(grokStatement); + let sensorParserInfo = new SensorParserConfigHistory(); + let sensorParserConfig = new SensorParserConfig(); + sensorParserConfig.parserConfig = {}; + + sensorParserConfig.parserConfig['grokPath'] = '/squid/grok/path'; + sensorParserInfo.config = sensorParserConfig; + + component.sensorParserConfigHistory = sensorParserInfo; + component.setGrokStatement(); + + expect(component.grokStatement).toEqual(grokStatement); + })); + + it('setTransformsConfigKeys/getTransformsOutput should return the keys of the transforms config ', async(() => { + let sensorParserInfo = new SensorParserConfigHistory(); + let sensorParserConfig = new SensorParserConfig(); + let fieldTransformer1 = new FieldTransformer(); + let fieldTransformer2 = new FieldTransformer(); + + fieldTransformer1.config = {'a': 'abc', 'x': 'xyz'}; + fieldTransformer1.output = ['a', 'b', 'c']; + fieldTransformer2.config = {'x': 'klm', 'b': 'def'}; + fieldTransformer2.output = ['a', 'b', 'c']; + sensorParserConfig.fieldTransformations = [fieldTransformer1, fieldTransformer2]; + sensorParserInfo.config = sensorParserConfig; + + component.setTransformsConfigKeys(); + let transformsOutput = component.getTransformsOutput(); + + expect(component.transformsConfigKeys.length).toEqual(0); + expect(component.transformsConfigKeys).toEqual([]); + expect(component.transformsConfigMap).toEqual({}); + expect(transformsOutput).toEqual('-'); + + component.sensorParserConfigHistory = sensorParserInfo; + component.setTransformsConfigKeys(); + transformsOutput = component.getTransformsOutput(); + + expect(component.transformsConfigKeys.length).toEqual(3); + expect(component.transformsConfigKeys).toEqual(['a', 'b', 'x']); + expect(component.transformsConfigMap).toEqual({'a': ['abc'], 'b': ['def'], 'x': ['xyz', 'klm']}); + expect(transformsOutput).toEqual('a, b, c'); + })); + + it('goBack should navigate to sensors page', async(() => { + router.navigateByUrl = jasmine.createSpy('navigateByUrl'); + + component.goBack(); + + expect(router.navigateByUrl).toHaveBeenCalledWith('/sensors'); + })); + + it('onEditSensor should navigate to sensor edit', async(() => { + router.navigateByUrl = jasmine.createSpy('navigateByUrl'); + + component.selectedSensorName = 'abc'; + + component.onEditSensor(); + expect(router.navigateByUrl).toHaveBeenCalledWith('/sensors(dialog:sensors-config/abc)'); + })); + + it('should set sensorEnrichmentConfig and aggregationConfigKeys to be initialised', async(() => { + let threatIntel = { + 'fieldMap': { + 'hbaseThreatIntel': [ 'ip_dst_addr', 'ip_src_addr', 'action'] + }, + 'fieldToTypeMap': { + 'ip_dst_addr': [ 'malicious_ip'], 'ip_src_addr': [ 'malicious_ip'], 'action': [ 'malicious_ip'] + }, + 'config': {}, + 'triageConfig': { + 'riskLevelRules': [ + { + 'rule': 'IN_SUBNET(ip_dst_addr, \'192.168.0.0/24\')', + 'score': 3 + }, + { + 'rule': 'user.type in [ \'admin\', \'power\' ] and asset.type == \'web\'', + 'score': 3 + }, + ], + 'aggregator': 'MAX', + 'aggregationConfig': {} + } + }; + let expected = [{'rule': 'IN_SUBNET(ip_dst_addr, \'192.168.0.0/24\')', 'score': 3}, + {'rule': 'user.type in [ \'admin\', \'power\' ] and asset.type == \'web\'', 'score': 3}]; + + let sensorEnrichmentConfig = new SensorEnrichmentConfig(); + sensorEnrichmentConfig.threatIntel = Object.assign(new ThreatIntelConfig(), threatIntel); + sensorEnrichmentConfigService.setForTest(sensorEnrichmentConfig); + + component.getEnrichmentData(); + + + expect(component.sensorEnrichmentConfig).toEqual(sensorEnrichmentConfig); + expect(component.rules).toEqual(expected); + })); + + let setDataForSensorOperation = function () { + let sensorParserInfo = new SensorParserConfigHistory(); + let sensorParserConfig = new SensorParserConfig(); + let kafkaTopic = new KafkaTopic(); + let topologyStatus = new TopologyStatus(); + + sensorParserConfig.sensorTopic = 'bro'; + sensorParserConfig.parserClassName = 'org.apache.metron.parsers.GrokParser'; + sensorParserConfig.parserConfig = {grokPattern: 'SQUID_DELIMITED squid grok statement'}; + sensorParserInfo.config = sensorParserConfig; + + kafkaTopic.name = 'bro'; + kafkaTopic.numPartitions = 1; + kafkaTopic.replicationFactor = 1; + + topologyStatus.name = 'bro'; + topologyStatus.latency = 10.1; + topologyStatus.throughput = 15.2; + + let broEnrichment = { + 'fieldMap': { + 'geo': ['ip_dst_addr'], + 'host': ['ip_dst_addr'], + 'whois': [], + 'stellar': {'config': {'group1': {}}} + }, + 'fieldToTypeMap': {}, 'config': {} + }; + let broThreatIntel = {'threatIntel': { + 'fieldMap': { 'hbaseThreatIntel': ['ip_dst_addr'] }, + 'fieldToTypeMap': { 'ip_dst_addr': ['malicious_ip'] } + } + }; + let broEnrichments = new SensorEnrichmentConfig(); + broEnrichments.enrichment = Object.assign(new EnrichmentConfig(), broEnrichment); + broEnrichments.threatIntel = Object.assign(new ThreatIntelConfig(), broThreatIntel); + + kafkaService.setForTest(kafkaTopic); + stormService.setForTest(topologyStatus); + sensorEnrichmentConfigService.setForTest(broEnrichments); + sensorParserConfigHistoryService.setForTest(sensorParserInfo); + }; + + it('onStartSensor should start sensor', async(() => { + spyOn(stormService, 'startParser').and.returnValue(Observable.create(observer => { + observer.next({}); + observer.complete(); + })); + + alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage'); + setDataForSensorOperation(); + + component.selectedSensorName = 'abc'; + + component.onStartSensor(); + + expect(stormService.startParser).toHaveBeenCalledWith('abc'); + expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Started sensor abc'); + })); + + it('onStopSensor should stop the sensor', async(() => { + spyOn(stormService, 'stopParser').and.returnValue(Observable.create(observer => { + observer.next({}); + observer.complete(); + })); + + alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage'); + setDataForSensorOperation(); + + component.selectedSensorName = 'abc'; + + component.onStopSensor(); + + expect(stormService.stopParser).toHaveBeenCalledWith('abc'); + expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Stopped sensor abc'); + })); + + it('onEnableSensor should enable sensor', async(() => { + spyOn(stormService, 'activateParser').and.returnValue(Observable.create(observer => { + observer.next({}); + observer.complete(); + })); + + alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage'); + setDataForSensorOperation(); + + component.selectedSensorName = 'abc'; + + component.onEnableSensor(); + + expect(stormService.activateParser).toHaveBeenCalledWith('abc'); + expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Enabled sensor abc'); + })); + + it('onDisableSensor should disable the sensor', async(() => { + spyOn(stormService, 'deactivateParser').and.returnValue(Observable.create(observer => { + observer.next({}); + observer.complete(); + })); + + alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage'); + setDataForSensorOperation(); + + component.selectedSensorName = 'abc'; + + component.onDisableSensor(); + + expect(stormService.deactivateParser).toHaveBeenCalledWith('abc'); + expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Disabled sensor abc'); + })); + + it('onDeleteSensor should delete the sensor', async(() => { + spyOn(sensorParserConfigService, 'deleteSensorParserConfig').and.returnValue(Observable.create(observer => { + observer.next({}); + observer.complete(); + })); + + alerts.showSuccessMessage = jasmine.createSpy('showSuccessMessage'); + router.navigateByUrl = jasmine.createSpy('navigateByUrl'); + setDataForSensorOperation(); + + component.selectedSensorName = 'abc'; + + component.onDeleteSensor(); + + expect(sensorParserConfigService.deleteSensorParserConfig).toHaveBeenCalledWith('abc'); + expect(alerts.showSuccessMessage).toHaveBeenCalledWith('Deleted sensor abc'); + expect(router.navigateByUrl).toHaveBeenCalledWith('/sensors'); + })); + + it('toggleStartStopInProgress should toggle the variable for showing progressbar', async(() => { + expect(component.startStopInProgress).toEqual(false); + + component.startStopInProgress = true; + expect(component.startStopInProgress).toEqual(true); + + component.startStopInProgress = false; + expect(component.startStopInProgress).toEqual(false); + })); + + it('should toggleTransformLink', async(() => { + expect(component.transformLinkText).toEqual('show more'); + + component.toggleTransformLink(); + expect(component.transformLinkText).toEqual('show less'); + + component.toggleTransformLink(); + expect(component.transformLinkText).toEqual('show more'); + })); + + it('should toggleThreatTriageLink', async(() => { + expect(component.threatTriageLinkText).toEqual('show more'); + + component.toggleThreatTriageLink(); + expect(component.threatTriageLinkText).toEqual('show less'); + + component.toggleThreatTriageLink(); + expect(component.threatTriageLinkText).toEqual('show more'); + })); + + it('should hide start', async(() => { + component.topologyStatus.status = 'ACTIVE'; + expect(component.isStartHidden()).toEqual(true); + + component.topologyStatus.status = 'INACTIVE'; + expect(component.isStartHidden()).toEqual(true); + + component.topologyStatus.status = 'Stopped'; + expect(component.isStartHidden()).toEqual(false); + + component.topologyStatus.status = 'KILLED'; + expect(component.isStartHidden()).toEqual(false); + })); + + it('should hide stop', async(() => { + component.topologyStatus.status = 'ACTIVE'; + expect(component.isStopHidden()).toEqual(false); + + component.topologyStatus.status = 'INACTIVE'; + expect(component.isStopHidden()).toEqual(false); + + component.topologyStatus.status = 'Stopped'; + expect(component.isStopHidden()).toEqual(true); + + component.topologyStatus.status = 'KILLED'; + expect(component.isStopHidden()).toEqual(true); + })); + + it('should hide enable', async(() => { + component.topologyStatus.status = 'ACTIVE'; + expect(component.isEnableHidden()).toEqual(true); + + component.topologyStatus.status = 'INACTIVE'; + expect(component.isEnableHidden()).toEqual(false); + + component.topologyStatus.status = 'Stopped'; + expect(component.isEnableHidden()).toEqual(true); + + component.topologyStatus.status = 'KILLED'; + expect(component.isEnableHidden()).toEqual(true); + })); + + it('should hide disable', async(() => { + component.topologyStatus.status = 'ACTIVE'; + expect(component.isDisableHidden()).toEqual(false); + + component.topologyStatus.status = 'INACTIVE'; + expect(component.isDisableHidden()).toEqual(true); + + component.topologyStatus.status = 'Stopped'; + expect(component.isDisableHidden()).toEqual(true); + + component.topologyStatus.status = 'KILLED'; + expect(component.isDisableHidden()).toEqual(true); + })); + +});
http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.ts new file mode 100644 index 0000000..7edc2c5 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.component.ts @@ -0,0 +1,372 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Component, OnInit} from '@angular/core'; +import {KafkaService} from '../../service/kafka.service'; +import {Router, ActivatedRoute} from '@angular/router'; +import {KafkaTopic} from '../../model/kafka-topic'; +import {MetronAlerts} from '../../shared/metron-alerts'; +import {SensorParserConfigService} from '../../service/sensor-parser-config.service'; +import {StormService} from '../../service/storm.service'; +import {TopologyStatus} from '../../model/topology-status'; +import {SensorParserConfigHistoryService} from '../../service/sensor-parser-config-history.service'; +import {SensorParserConfigHistory} from '../../model/sensor-parser-config-history'; +import {SensorEnrichmentConfigService} from '../../service/sensor-enrichment-config.service'; +import {SensorEnrichmentConfig} from '../../model/sensor-enrichment-config'; +import {RiskLevelRule} from '../../model/risk-level-rule'; +import {HdfsService} from '../../service/hdfs.service'; +import {RestError} from '../../model/rest-error'; +import {GrokValidationService} from '../../service/grok-validation.service'; + +@Component({ + selector: 'metron-config-sensor-parser-readonly', + templateUrl: 'sensor-parser-config-readonly.component.html', + styleUrls: ['sensor-parser-config-readonly.component.scss'] +}) +export class SensorParserConfigReadonlyComponent implements OnInit { + + selectedSensorName: string; + startStopInProgress: boolean = false; + kafkaTopic: KafkaTopic = new KafkaTopic(); + sensorParserConfigHistory: SensorParserConfigHistory = new SensorParserConfigHistory(); + topologyStatus: TopologyStatus = new TopologyStatus(); + sensorEnrichmentConfig: SensorEnrichmentConfig = new SensorEnrichmentConfig(); + grokStatement: string = ''; + transformsConfigKeys: string[] = []; + transformsConfigMap: {} = {}; + rules: RiskLevelRule[] = []; + transformLinkText = 'show more'; + threatTriageLinkText = 'show more'; + + editViewMetaData: {label?: string, value?: string, type?: string, model?: string, boldTitle?: boolean}[] = [ + {type: 'SEPARATOR', model: '', value: ''}, + {label: 'PARSER', model: 'sensorParserConfigHistory', value: 'parserClassName'}, + {label: 'LAST UPDATED', model: 'sensorParserConfigHistory', value: 'modifiedByDate'}, + {label: 'LAST EDITOR', model: 'sensorParserConfigHistory', value: 'modifiedBy'}, + {label: 'STATE', model: 'topologyStatus', value: 'sensorStatus'}, + {label: 'ORIGINATOR', model: 'sensorParserConfigHistory', value: 'createdBy'}, + {label: 'CREATION DATE', model: 'sensorParserConfigHistory', value: 'createdDate'}, + + {type: 'SPACER', model: '', value: ''}, + + {label: 'STORM', model: 'topologyStatus', value: 'status', boldTitle: true}, + {label: 'LATENCY', model: 'topologyStatus', value: 'latency'}, + {label: 'THROUGHPUT', model: 'topologyStatus', value: 'throughput'}, + {label: 'EMITTED(10 MIN)', model: 'topologyStatus', value: 'emitted'}, + {label: 'ACKED(10 MIN)', model: 'topologyStatus', value: 'acked'}, + + {type: 'SPACER', model: '', value: ''}, + + {label: 'KAFKA', model: 'kafkaTopic', value: 'currentKafkaStatus', boldTitle: true}, + {label: 'PARTITONS', model: 'kafkaTopic', value: 'numPartitions'}, + {label: 'REPLICATION FACTOR', model: 'kafkaTopic', value: 'replicationFactor'}, + {type: 'SEPARATOR', model: '', value: ''}, + + {label: '', model: 'grokStatement', value: 'grokPattern'}, + + {type: 'TITLE', model: '', value: 'Schema'}, + {label: '', model: 'transforms', value: ''}, + {type: 'SEPARATOR', model: '', value: ''}, + + {type: 'TITLE', model: '', value: 'Threat Triage Rules'}, + {label: '', model: 'threatTriageRules', value: ''} + + ]; + + constructor(private sensorParserConfigHistoryService: SensorParserConfigHistoryService, + private sensorParserConfigService: SensorParserConfigService, + private sensorEnrichmentService: SensorEnrichmentConfigService, + private stormService: StormService, + private kafkaService: KafkaService, + private hdfsService: HdfsService, + private grokValidationService: GrokValidationService, + private activatedRoute: ActivatedRoute, private router: Router, + private metronAlerts: MetronAlerts) { + } + + getSensorInfo(): void { + this.sensorParserConfigHistoryService.get(this.selectedSensorName).subscribe( + (results: SensorParserConfigHistory) => { + this.sensorParserConfigHistory = results; + this.setGrokStatement(); + this.setTransformsConfigKeys(); + + let items = this.sensorParserConfigHistory.config.parserClassName.split('.'); + this.sensorParserConfigHistory['parserClassName'] = items[items.length - 1].replace('Basic', '').replace('Parser', ''); + + }); + } + + getSensorStatusService() { + this.stormService.getStatus(this.selectedSensorName).subscribe( + (results: TopologyStatus) => { + this.topologyStatus = results; + }, + error => { + this.topologyStatus.status = 'Stopped'; + }); + } + + getKafkaData(): void { + this.kafkaService.get(this.selectedSensorName).subscribe( + (results: KafkaTopic) => { + this.kafkaTopic = results; + this.kafkaService.sample(this.selectedSensorName).subscribe((sampleData: string) => { + this.kafkaTopic['currentKafkaStatus'] = (sampleData && sampleData.length > 0) ? 'Emitting' : 'Not Emitting'; + }, + error => { + this.kafkaTopic['currentKafkaStatus'] = 'Not Emitting'; + }); + }, error => { + this.kafkaTopic['currentKafkaStatus'] = 'No Kafka Topic'; + }); + } + + getEnrichmentData() { + this.sensorEnrichmentService.get(this.selectedSensorName).subscribe((sensorEnrichmentConfig) => { + this.sensorEnrichmentConfig = sensorEnrichmentConfig; + this.rules = sensorEnrichmentConfig.threatIntel.triageConfig.riskLevelRules; + }); + } + + getTopologyStatus(key: string): string { + if (key === 'latency') { + return this.topologyStatus.latency >= 0? (this.topologyStatus.latency + 's') : '-'; + } else if (key === 'throughput') { + return this.topologyStatus.throughput >= 0 ? ((Math.round(this.topologyStatus.throughput * 100) / 100) + 'kb/s') : '-'; + } else if (key === 'emitted') { + return this.topologyStatus.emitted >= 0 ? (this.topologyStatus.emitted + '') : '-'; + } else if (key === 'acked') { + return this.topologyStatus.acked >= 0 ? (this.topologyStatus.acked + '') : '-'; + } else if (key === 'sensorStatus') { + if (this.topologyStatus.status === 'ACTIVE') { + return 'Enabled'; + } else if (this.topologyStatus.status === 'INACTIVE') { + return 'Disabled'; + } else { + return '-'; + } + } else if (key === 'status') { + if (this.topologyStatus.status === 'ACTIVE') { + return 'Running'; + } else if (this.topologyStatus.status === 'INACTIVE') { + return 'Disabled'; + } else { + return 'Stopped'; + } + } + + return this.topologyStatus[key] ? this.topologyStatus[key] : '-'; + } + + ngOnInit() { + this.activatedRoute.params.subscribe(params => { + this.selectedSensorName = params['id']; + this.getData(); + }); + } + + getData() { + this.startStopInProgress = false; + + this.getSensorInfo(); + this.getSensorStatusService(); + this.getKafkaData(); + this.getEnrichmentData(); + } + + setGrokStatement() { + if (this.sensorParserConfigHistory.config && this.sensorParserConfigHistory.config.parserConfig) { + let path = this.sensorParserConfigHistory.config.parserConfig['grokPath']; + if (path) { + this.hdfsService.read(path).subscribe(contents => { + this.grokStatement = contents; + }, (hdfsError: RestError) => { + this.grokValidationService.getStatement(path).subscribe(contents => { + this.grokStatement = contents; + }, (grokError: RestError) => { + this.metronAlerts.showErrorMessage('Could not find grok statement in HDFS or classpath at ' + path); + }); + }); + } + } + } + + setTransformsConfigKeys() { + if (this.sensorParserConfigHistory.config && this.sensorParserConfigHistory.config.fieldTransformations && + this.sensorParserConfigHistory.config.fieldTransformations.length > 0) { + this.transformsConfigKeys = []; + for (let transforms of this.sensorParserConfigHistory.config.fieldTransformations) { + if (transforms.config) { + for (let key of Object.keys(transforms.config)) { + if (this.transformsConfigKeys.indexOf(key) === -1) { + this.transformsConfigMap[key] = []; + this.transformsConfigKeys.push(key); + } + this.transformsConfigMap[key].push(transforms.config[key]); + } + } + } + this.transformsConfigKeys = this.transformsConfigKeys.sort(); + } + } + + getTransformsOutput(): string { + if (this.sensorParserConfigHistory.config && this.sensorParserConfigHistory.config.fieldTransformations && + this.sensorParserConfigHistory.config.fieldTransformations.length > 0) { + let output = []; + for (let transforms of this.sensorParserConfigHistory.config.fieldTransformations) { + if (transforms.output) { + output = output.concat(transforms.output); + } + } + output = output.sort().filter(function(item, pos, self) { + return self.indexOf(item) === pos; + }); + + return output.join(', '); + } + + return '-'; + } + + goBack() { + this.router.navigateByUrl('/sensors'); + } + + onEditSensor() { + this.router.navigateByUrl('/sensors(dialog:sensors-config/' + this.selectedSensorName + ')'); + } + + onStartSensor() { + this.toggleStartStopInProgress(); + let name = this.selectedSensorName; + + this.stormService.startParser(name).subscribe(result => { + this.metronAlerts.showSuccessMessage('Started sensor ' + name); + this.toggleStartStopInProgress(); + this.getData(); + }, + error => { + this.metronAlerts.showErrorMessage('Unable to start sensor ' + name); + this.toggleStartStopInProgress(); + }); + } + + onStopSensor() { + this.toggleStartStopInProgress(); + + let name = this.selectedSensorName; + this.stormService.stopParser(name).subscribe(result => { + this.metronAlerts.showSuccessMessage('Stopped sensor ' + name); + this.toggleStartStopInProgress(); + this.getData(); + }, + error => { + this.metronAlerts.showErrorMessage('Unable to stop sensor ' + name); + this.toggleStartStopInProgress(); + }); + } + + onEnableSensor() { + this.toggleStartStopInProgress(); + + let name = this.selectedSensorName; + this.stormService.activateParser(name).subscribe(result => { + this.metronAlerts.showSuccessMessage('Enabled sensor ' + name); + this.toggleStartStopInProgress(); + this.getData(); + }, + error => { + this.metronAlerts.showErrorMessage('Unable to enabled sensor ' + name); + this.toggleStartStopInProgress(); + }); + } + + onDisableSensor() { + this.toggleStartStopInProgress(); + + let name = this.selectedSensorName; + this.stormService.deactivateParser(name).subscribe(result => { + this.metronAlerts.showSuccessMessage('Disabled sensor ' + name); + this.toggleStartStopInProgress(); + this.getData(); + }, + error => { + this.metronAlerts.showErrorMessage('Unable to disable sensor ' + name); + this.toggleStartStopInProgress(); + }); + } + + onDeleteSensor() { + this.toggleStartStopInProgress(); + + let name = this.selectedSensorName; + this.sensorParserConfigService.deleteSensorParserConfig(name).subscribe(result => { + this.metronAlerts.showSuccessMessage('Deleted sensor ' + name); + this.toggleStartStopInProgress(); + this.sensorParserConfigService.dataChangedSource.next([this.sensorParserConfigHistory.config]); + this.goBack(); + }, + error => { + this.metronAlerts.showErrorMessage('Unable to delete sensor ' + name); + this.toggleStartStopInProgress(); + }); + } + + toggleStartStopInProgress() { + this.startStopInProgress = !this.startStopInProgress; + } + + getRuleDisplayName(): string { + return this.rules.map(x => this.getDisplayName(x)).join(', '); + } + + getDisplayName(riskLevelRule: RiskLevelRule): string { + if (riskLevelRule.name) { + return riskLevelRule.name; + } else { + return riskLevelRule.rule ? riskLevelRule.rule : ''; + } + } + + toggleTransformLink() { + return this.transformLinkText = (this.transformLinkText === 'show more') ? 'show less' : 'show more'; + } + + toggleThreatTriageLink() { + return this.threatTriageLinkText = (this.threatTriageLinkText === 'show more') ? 'show less' : 'show more'; + } + + isStartHidden() { + return (this.topologyStatus.status === 'ACTIVE' || this.topologyStatus.status === 'INACTIVE'); + } + + isStopHidden() { + return ((this.topologyStatus.status === 'KILLED' || this.topologyStatus.status === 'Stopped')); + } + + isEnableHidden() { + return (this.topologyStatus.status === 'ACTIVE' || this.topologyStatus.status === 'KILLED' + || this.topologyStatus.status === 'Stopped'); + } + + isDisableHidden() { + return (this.topologyStatus.status === 'INACTIVE' || this.topologyStatus.status === 'KILLED' + || this.topologyStatus.status === 'Stopped'); + } +} http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.module.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.module.ts new file mode 100644 index 0000000..c20d377 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.module.ts @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NgModule } from '@angular/core'; +import {routing} from './sensor-parser-config-readonly.routing'; +import {SharedModule} from '../../shared/shared.module'; +import {SensorParserConfigReadonlyComponent} from './sensor-parser-config-readonly.component'; + +@NgModule ({ + imports: [ routing, SharedModule ], + declarations: [ SensorParserConfigReadonlyComponent ] +}) +export class SensorParserConfigReadonlyModule { } http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.routing.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.routing.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.routing.ts new file mode 100644 index 0000000..71e1bad --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config-readonly/sensor-parser-config-readonly.routing.ts @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ModuleWithProviders } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import {SensorParserConfigReadonlyComponent} from './sensor-parser-config-readonly.component'; +import {AuthGuard} from '../../shared/auth-guard'; + +export const routing: ModuleWithProviders = RouterModule.forChild([ + { path: 'sensors-readonly/:id', component: SensorParserConfigReadonlyComponent, canActivate: [AuthGuard], outlet: 'dialog'} +]); http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config/index.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config/index.ts b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/index.ts new file mode 100644 index 0000000..f108057 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/index.ts @@ -0,0 +1 @@ +export * from './sensor-parser-config.component'; http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.html b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.html new file mode 100644 index 0000000..33e8fd5 --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.html @@ -0,0 +1,181 @@ +<!-- + Licensed to the Apache Software + Foundation (ASF) under one or more contributor license agreements. See the + NOTICE file distributed with this work for additional information regarding + copyright ownership. The ASF licenses this file to You under the Apache License, + Version 2.0 (the "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed + under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + OR CONDITIONS OF ANY KIND, either express or implied. See the License for + the specific language governing permissions and limitations under the License. + --> +<metron-config-metron-modal> + <metron-config-sensor-grok [hidden]="!showGrokValidator" [showGrok]="showGrokValidator" + [grokStatement]="grokStatement" + [(patternLabel)]="patternLabel" + [(sensorParserConfig)]="sensorParserConfig" + (hideGrok)="hidePane(pane.GROK)" (onSaveGrokStatement)="onSaveGrokStatement($event)" (onSavePatternLabel)="onSavePatternLabel($event)"></metron-config-sensor-grok> + + <metron-config-sensor-raw-json [hidden]="!showRawJson" [showRawJson]="showRawJson" (hideRawJson)="hidePane(pane.RAWJSON)" + [(sensorParserConfig)]="sensorParserConfig" + [(sensorEnrichmentConfig)]="sensorEnrichmentConfig" + [(indexingConfigurations)]="indexingConfigurations" + (onRawJsonChanged)="onRawJsonChanged()"></metron-config-sensor-raw-json> + + <metron-config-sensor-field-schema [hidden]="!showFieldSchema" [showFieldSchema]="showFieldSchema" + [grokStatement]="grokStatement" + [(sensorParserConfig)]="sensorParserConfig" + [(sensorEnrichmentConfig)]="sensorEnrichmentConfig" + (hideFieldSchema)="hidePane(pane.FIELDSCHEMA)"></metron-config-sensor-field-schema> + + <metron-config-sensor-threat-triage [hidden]="!showThreatTriage" [showThreatTriage]="showThreatTriage" + [(sensorEnrichmentConfig)]="sensorEnrichmentConfig" + (hideThreatTriage)="hidePane(pane.THREATTRIAGE)"></metron-config-sensor-threat-triage> + + + <div class="metron-slider-pane-edit fill load-right-to-left dialog1x" style="overflow: auto" > + <div style="height:100%"> + <div class="form-title">{{sensorParserConfig.sensorTopic}} </div> + <i class="fa fa-times pull-right main close-button" aria-hidden="true" (click)="goBack()"></i> + + <form role="form" [formGroup]="sensorConfigForm"> + <div class="form-group"> + <label attr.for="sensorTopic">NAME * </label> + <input type="text" class="form-control" name="sensorTopic" formControlName="sensorTopic" [(ngModel)]="sensorParserConfig.sensorTopic" (ngModelChange)="onSetSensorName()" [readonly]="editMode"> + <label *ngIf="currentKafkaStatus !== null && currentKafkaStatus === kafkaStatus.NO_TOPIC"><span class="warning-text"> No Matching Kafka Topic </span></label> + <label *ngIf="currentKafkaStatus !== null && currentKafkaStatus === kafkaStatus.EMITTING" ><span class="success-text-color"> Kafka Topic Exists. Emitting </span></label> + <label *ngIf="currentKafkaStatus !== null && currentKafkaStatus === kafkaStatus.NOT_EMITTING"><span class="success-text-color"> Kafka Topic Exists. </span><span class="warning-text" > Not Emitting </span></label> + </div> + + <div class="form-group"> + <label attr.for="parserClassName">PARSER TYPE * </label> + <select class="form-control" formControlName="parserClassName" [(ngModel)]="sensorParserConfig.parserClassName" (ngModelChange)="onParserTypeChange()" > + <option *ngFor="let parserName of availableParserNames" [value]="availableParsers[parserName]">{{parserName}}</option> + </select> + </div> + + <div class="form-group" [ngClass]="{'panel-selected': showGrokValidator }" *ngIf="isGrokParser(sensorParserConfig)" > + <label attr.for="grokStatement">GROK STATEMENT</label> + <div class="input-group" [attr.disabled]="!sensorNameValid || !parserClassValid"> + <input type="text" class="form-control" formControlName="grokStatement" [(ngModel)]="this.grokStatement" (ngModelChange)="onGrokStatementChange()" readonly> + <span class="input-group-btn"> + <button class="btn btn-default" type="button" (click)="sensorNameValid && parserClassValid && onShowGrokPane()" readonly> + <i class="fa fa-columns" aria-hidden="true"></i> + <i class="fa fa-angle-double-right" style="padding-left: 3px" aria-hidden="true"></i> + </button> + </span> + </div> + </div> + + <div class="form-group" [ngClass]="{'panel-selected': showFieldSchema }" > + <label attr.for="fieldSchema">SCHEMA</label> + <div class="input-group" [attr.disabled]="!configValid"> + <div class="form-control" style="height: 80px; resize: none;" readonly> + <table cellspacing="10"> + <tr> <td class="p-l-1">TRANSFORMATIONS </td> <td class="p-l-1"> </td><td class="p-1-1">{{getTransformationCount()}}</td> </tr> + <tr> <td>ENRICHMENTS</td> <td class="p-l-1"> </td> <td class="p-1-1">{{getEnrichmentCount()}}</td></tr> + <tr> <td>THREAT INTEL</td> <td class="p-l-1"> </td> <td class="p-1-1">{{getThreatIntelCount()}}</td></tr> + </table> + </div> + <span class="input-group-btn"> + <button class="btn btn-default" type="button" (click)="configValid && showPane(pane.FIELDSCHEMA)" style="height: 80px;" readonly> + <i class="fa fa-columns" aria-hidden="true"></i> + <i class="fa fa-angle-double-right" style="padding-left: 3px" aria-hidden="true"></i> + </button> + </span> + </div> + </div> + + <div class="form-group" [ngClass]="{'panel-selected': showThreatTriage }"> + <label attr.for="stellar">THREAT TRIAGE</label> + <div class="input-group" [attr.disabled]="!configValid"> + <div class="form-control" style="resize: none;" readonly> + <table style="margin: 0"> + <tr> <td class="p-l-1">RULES </td> <td class="p-l-1"> </td><td class="p-1-1">{{getRuleCount()}}</td> </tr> + </table> + </div> + <span class="input-group-btn"> + <button class="btn btn-default" type="button" (click)="configValid && showPane(pane.THREATTRIAGE)" readonly> + <i class="fa fa-columns" aria-hidden="true"></i> + <i class="fa fa-angle-double-right" style="padding-left: 3px" aria-hidden="true"></i> + </button> + </span> + </div> + </div> + + <div [hidden]="!showAdvancedParserConfiguration"> + <div class="form-group"> + <div class="form-seperator-edit"></div> + <div class="advanced-title">Advanced</div> + <i class="fa fa-times pull-right small-close-button" aria-hidden="true" (click)="onAdvancedConfigFormClose()"></i> + </div> + <div class="form-group" [ngClass]="{'panel-selected': showRawJson }"> + <label attr.for="stellar">RAW JSON</label> + <div class="input-group"> + <div class="form-control" style="resize: none;" readonly>Select</div> + <span class="input-group-btn"> + <button class="btn btn-default" type="button" (click)="showPane(pane.RAWJSON)" readonly> + <i class="fa fa-columns" aria-hidden="true"></i> + <i class="fa fa-angle-double-right" style="padding-left: 3px" aria-hidden="true"></i> + </button> + </span> + </div> + </div> + + <div class="form-group"> + <label attr.for="index">HDFS INDEX NAME</label> + <input type="text" class="form-control" name="hdfsIndex" formControlName="hdfsIndex" [(ngModel)]="indexingConfigurations.hdfs.index" > + </div> + <div class="form-group"> + <label attr.for="batchSize">HDFS BATCH SIZE</label> + <metron-config-number-spinner name="hdfsBatchSize" [(ngModel)]="indexingConfigurations.hdfs.batchSize" formControlName="hdfsBatchSize"> </metron-config-number-spinner> + </div> + <div class="form-group"> + <label attr.for="index">HDFS ENABLED</label> + <input type="checkbox" class="form-control" name="hdfsEnabled" formControlName="hdfsEnabled" [(ngModel)]="indexingConfigurations.hdfs.enabled" > + </div> + <div class="form-group"> + <label attr.for="index">ELASTICSEARCH INDEX NAME</label> + <input type="text" class="form-control" name="elasticsearchIndex" formControlName="elasticsearchIndex" [(ngModel)]="indexingConfigurations.elasticsearch.index" > + </div> + <div class="form-group"> + <label attr.for="batchSize">ELASTICSEARCH BATCH SIZE</label> + <metron-config-number-spinner name="elasticsearchBatchSize" [(ngModel)]="indexingConfigurations.elasticsearch.batchSize" formControlName="elasticsearchBatchSize"> </metron-config-number-spinner> + </div> + <div class="form-group"> + <label attr.for="index">ELASTICSEARCH ENABLED</label> + <input type="checkbox" class="form-control" name="elasticsearchEnabled" formControlName="elasticsearchEnabled" [(ngModel)]="indexingConfigurations.elasticsearch.enabled" > + </div> + <div class="form-group"> + <label attr.for="index">SOLR INDEX NAME</label> + <input type="text" class="form-control" name="solrIndex" formControlName="solrIndex" [(ngModel)]="indexingConfigurations.solr.index" > + </div> + <div class="form-group"> + <label attr.for="batchSize">SOLR BATCH SIZE</label> + <metron-config-number-spinner name="solrBatchSize" [(ngModel)]="indexingConfigurations.solr.batchSize" formControlName="solrBatchSize"> </metron-config-number-spinner> + </div> + <div class="form-group"> + <label attr.for="index">SOLR ENABLED</label> + <input type="checkbox" class="form-control" name="solrEnabled" formControlName="solrEnabled" [(ngModel)]="indexingConfigurations.solr.enabled" > + </div> + <div class="form-group"> + <label attr.for="parserConfig">PARSER CONFIG</label> + <metron-config-advanced-form name="parserConfig" [(config)]="sensorParserConfig.parserConfig"></metron-config-advanced-form> + </div> + + </div> + + <div class="form-group"> + <div class="form-seperator-edit"></div> + <div class="button-row"> + <button type="submit" class="btn save-button" [ngClass]="{'disabled':!configValid}" (click)="onSave()">SAVE</button> + <button class="btn form-enable-disable-button" (click)="goBack()" >CANCEL</button> + <span class="advanced-link" [hidden]="showAdvancedParserConfiguration" (click)="showAdvancedParserConfiguration = true">Advanced</span> + </div> + </div> + </form> + </div> + </div> +</metron-config-metron-modal> http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/1ef8cd8f/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.scss b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.scss new file mode 100644 index 0000000..298163a --- /dev/null +++ b/metron-interface/metron-config/src/app/sensors/sensor-parser-config/sensor-parser-config.component.scss @@ -0,0 +1,120 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../_variables.scss"; +@import "../../../styles.scss"; + +.form-title +{ + padding-left: 25px; +} + +.form-group +{ + padding-left: 25px; + padding-right: 20px; + padding-bottom: 10px; +} + +.close-button +{ + padding-right: 20px; +} + +.advanced-link { + padding-left: 10px; + cursor: pointer; + color: $field-button-color; + font-size: 14px; + +} + +.advanced-title { + font-size: 16px; + color: $form-field-text-color; + display: inline-block; +} + +.small-close-button { + font-size: 16px; + padding-right: 10px; + cursor: pointer; +} + +.input-placeholder { + font-size: 11px; + font-style: italic; + color:#999999; +} + +.metron-slider-pane-edit { + background: $edit-background; +} + +.input-group[disabled='true'] { + cursor: not-allowed; + opacity: .65; + background-color: #333333; + + .btn { + cursor: inherit; + } + + table tr td { + border: none !important; + } +} + +.button-container { + padding-top: 5px; +} + +.button-row { + padding-top: 10px; +} + +.form-enable-disable-button { + width: 32%; +} + +.save-button { + background-color: $form-button-border; + border-color: $form-button-border; + color: white; + font-size: 14px; + width: 32%; + + &:hover + { + + } + + &:focus + { + outline: none; + } +} + +.panel-selected { + background-color: $edit-background-border; +} + +metron-config-sensor-grok, metron-config-sensor-raw-json, metron-config-sensor-field-schema, +metron-config-sensor-threat-triage, metron-config-sensor-threat-triage +{ + @extend .flexbox-row-reverse; +}