http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.spec.ts b/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.spec.ts index 899a2b1..a953b32 100644 --- a/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.spec.ts +++ b/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.spec.ts @@ -16,7 +16,6 @@ * limitations under the License. */ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { Component, Input } from '@angular/core'; import { SavedSearchesComponent } from './saved-searches.component'; import { CollapseComponent } from '../../shared/collapse/collapse.component'; @@ -24,8 +23,8 @@ import { CenterEllipsesPipe } from '../../shared/pipes/center-ellipses.pipe'; import { ColumnNameTranslatePipe } from '../../shared/pipes/column-name-translate.pipe'; import { Router } from '@angular/router'; import { SaveSearchService } from '../../service/save-search.service'; -import { MetronDialogBox } from '../../shared/metron-dialog-box'; import { of } from 'rxjs'; +import { DialogService } from 'app/service/dialog.service'; describe('SavedSearchesComponent', () => { @@ -40,7 +39,7 @@ describe('SavedSearchesComponent', () => { listSavedSearches: jasmine.createSpy('listSavedSearches').and.returnValue(of([])), listRecentSearches: jasmine.createSpy('listRecentSearches').and.returnValue(of([])), } }, - MetronDialogBox + DialogService ], declarations: [ SavedSearchesComponent,
http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.ts b/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.ts index 2204179..ab182c0 100644 --- a/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.ts +++ b/metron-interface/metron-alerts/src/app/alerts/saved-searches/saved-searches.component.ts @@ -22,9 +22,10 @@ import {forkJoin as observableForkJoin} from 'rxjs'; import {SaveSearchService} from '../../service/save-search.service'; import {SaveSearch} from '../../model/save-search'; -import {MetronDialogBox} from '../../shared/metron-dialog-box'; import {NUM_SAVED_SEARCH} from '../../utils/constants'; import {CollapseComponentData, CollapseComponentDataItems} from '../../shared/collapse/collapse-component-data'; +import { DialogService } from 'app/service/dialog.service'; +import { ConfirmationType } from 'app/model/confirmation-type'; @Component({ selector: 'app-saved-searches', @@ -39,7 +40,7 @@ export class SavedSearchesComponent implements OnInit { recentSearches: CollapseComponentData = new CollapseComponentData(); constructor(private router: Router, private saveSearchService: SaveSearchService, - private metronDialog: MetronDialogBox) { + private dialogService: DialogService) { } doDeleteRecentSearch(selectedSearch: SaveSearch) { @@ -61,21 +62,33 @@ export class SavedSearchesComponent implements OnInit { } deleteRecentSearch($event) { - let selectedSearch = this.recentSearcheObj.find(savedSearch => savedSearch.name === $event.key); - this.metronDialog.showConfirmationMessage('Do you wish to delete recent search ' + selectedSearch.name).subscribe((result: boolean) => { - if (result) { - this.doDeleteRecentSearch(selectedSearch); - } - }); + let selectedSearch = this.recentSearcheObj.find( + savedSearch => savedSearch.name === $event.key + ); + const confirmedSubscription = this.dialogService + .launchDialog( + 'Do you wish to delete recent search ' + selectedSearch.name + ) + .subscribe(action => { + if (action === ConfirmationType.Confirmed) { + this.doDeleteRecentSearch(selectedSearch); + } + confirmedSubscription.unsubscribe(); + }); } deleteSearch($event) { - let selectedSearch = this.searches.find(savedSearch => savedSearch.name === $event.key); - this.metronDialog.showConfirmationMessage('Do you wish to delete saved search ' + selectedSearch.name).subscribe((result: boolean) => { - if (result) { - this.doDeleteSearch(selectedSearch); - } - }); + let selectedSearch = this.searches.find( + savedSearch => savedSearch.name === $event.key + ); + const confirmedSubscription = this.dialogService + .launchDialog('Do you wish to delete saved search ' + selectedSearch.name) + .subscribe(action => { + if (action === ConfirmationType.Confirmed) { + this.doDeleteSearch(selectedSearch); + } + confirmedSubscription.unsubscribe(); + }); } ngOnInit() { http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/app.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/app.component.html b/metron-interface/metron-alerts/src/app/app.component.html index c3a5d3c..d94a6d7 100644 --- a/metron-interface/metron-alerts/src/app/app.component.html +++ b/metron-interface/metron-alerts/src/app/app.component.html @@ -30,6 +30,7 @@ <router-outlet></router-outlet> </div> <router-outlet name="dialog" ></router-outlet> + <app-metron-dialog></app-metron-dialog> </div> http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/app.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/app.component.spec.ts b/metron-interface/metron-alerts/src/app/app.component.spec.ts index ce38c22..1485904 100644 --- a/metron-interface/metron-alerts/src/app/app.component.spec.ts +++ b/metron-interface/metron-alerts/src/app/app.component.spec.ts @@ -21,6 +21,8 @@ import { AppComponent } from './app.component'; import { Component } from '@angular/core'; import { AuthenticationService } from './service/authentication.service'; import { of } from 'rxjs'; +import { DialogService } from './service/dialog.service'; +import { MetronDialogComponent } from './shared/metron-dialog/metron-dialog.component'; @Component({ selector: 'router-outlet', template: '' }) class RouterOutletStubComponent {} @@ -32,10 +34,12 @@ describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ providers: [ + DialogService, { provide: AuthenticationService, useValue: { onLoginEvent: of(true) } } ], declarations: [ AppComponent, + MetronDialogComponent, RouterOutletStubComponent, ], }).compileComponents(); http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/app.module.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/app.module.ts b/metron-interface/metron-alerts/src/app/app.module.ts index 44e2650..a605b80 100644 --- a/metron-interface/metron-alerts/src/app/app.module.ts +++ b/metron-interface/metron-alerts/src/app/app.module.ts @@ -31,7 +31,6 @@ import {ConfigureTableService} from './service/configure-table.service'; import {SaveSearchModule} from './alerts/save-search/save-search.module'; import {SaveSearchService} from './service/save-search.service'; import {SavedSearchesModule} from './alerts/saved-searches/saved-searches.module'; -import {MetronDialogBox} from './shared/metron-dialog-box'; import {ConfigureRowsModule} from './alerts/configure-rows/configure-rows.module'; import {SwitchModule} from './shared/switch/switch.module'; import {ColumnNamesService} from './service/column-names.service'; @@ -47,6 +46,9 @@ import {MetaAlertsModule} from './alerts/meta-alerts/meta-alerts.module'; import {SearchService} from './service/search.service'; import { GlobalConfigService } from './service/global-config.service'; import { DefaultHeadersInterceptor } from './http-interceptors/default-headers.interceptor'; +import { DialogService } from './service/dialog.service'; +import { MetronDialogComponent } from './shared/metron-dialog/metron-dialog.component'; + import {PcapModule} from './pcap/pcap.module'; @@ -57,7 +59,8 @@ export function initConfig(config: ColumnNamesService) { @NgModule({ declarations: [ - AppComponent + AppComponent, + MetronDialogComponent ], imports: [ BrowserModule, @@ -84,11 +87,12 @@ export function initConfig(config: ColumnNamesService) { ConfigureTableService, SearchService, SaveSearchService, - MetronDialogBox, ColumnNamesService, UpdateService, MetaAlertService, - GlobalConfigService], + GlobalConfigService, + DialogService, + ], bootstrap: [AppComponent] }) http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/model/confirmation-type.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/confirmation-type.ts b/metron-interface/metron-alerts/src/app/model/confirmation-type.ts new file mode 100644 index 0000000..5441a9e --- /dev/null +++ b/metron-interface/metron-alerts/src/app/model/confirmation-type.ts @@ -0,0 +1,21 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export enum ConfirmationType { + Confirmed = 'Confirmed', + Rejected = 'Rejected' +} http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/model/dialog-type.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/model/dialog-type.ts b/metron-interface/metron-alerts/src/app/model/dialog-type.ts new file mode 100644 index 0000000..0cd398e --- /dev/null +++ b/metron-interface/metron-alerts/src/app/model/dialog-type.ts @@ -0,0 +1,21 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export enum DialogType { + Confirmation, + Error +} http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts index f767020..fdd75f7 100644 --- a/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts +++ b/metron-interface/metron-alerts/src/app/pcap/pcap-panel/pcap-panel.component.ts @@ -35,11 +35,6 @@ export class PcapPanelComponent implements OnInit, OnDestroy { pdml: Pdml = null; pcapRequest: PcapRequest; resetPaginationForSearch: boolean; - - statusSubscription: Subscription; - cancelSubscription: Subscription; - submitSubscription: Subscription; - getSubscription: Subscription; queryRunning = false; queryId: string; progressWidth = 0; @@ -47,28 +42,33 @@ export class PcapPanelComponent implements OnInit, OnDestroy { savedPcapRequest: {}; errorMsg: string; cancelConfirmMessage = 'Are you sure want to cancel the running query?'; + subscriptions: { + [key: string]: Subscription + } = {}; constructor(private pcapService: PcapService) { } ngOnInit() { this.pcapRequest = new PcapRequest(); - this.pcapService.getRunningJob().subscribe((statusResponses: PcapStatusResponse[]) => { + this.subscriptions['runningJobSubscription'] = this.pcapService.getRunningJob().subscribe((statusResponses: PcapStatusResponse[]) => { if (statusResponses.length > 0) { // Assume the first job in the list is the running job this.queryRunning = true; let statusResponse = statusResponses[0]; this.updateStatus(statusResponse); this.startPolling(statusResponse.jobId); - this.pcapService.getPcapRequest(statusResponse.jobId).subscribe((pcapRequest: PcapRequest) => { - this.pcapRequest = pcapRequest; - }); + this.subscriptions['pcapRequestSubscription'] = this.pcapService.getPcapRequest(statusResponse.jobId).subscribe( + (pcapRequest: PcapRequest) => { + this.pcapRequest = pcapRequest; + } + ); } }); } changePage(page) { this.pagination.selectedPage = page; - this.pcapService.getPackets(this.queryId, this.pagination.selectedPage).toPromise().then(pdml => { + this.subscriptions['packetSubscription'] = this.pcapService.getPackets(this.queryId, this.pagination.selectedPage).subscribe(pdml => { this.pdml = pdml; }); } @@ -81,26 +81,28 @@ export class PcapPanelComponent implements OnInit, OnDestroy { this.pdml = null; this.progressWidth = 0; this.errorMsg = null; - this.submitSubscription = this.pcapService.submitRequest(pcapRequest).subscribe((submitResponse: PcapStatusResponse) => { - let id = submitResponse.jobId; - if (!id) { - this.errorMsg = submitResponse.description; - this.queryRunning = false; - } else { - this.startPolling(id); + this.subscriptions['submitSubscription'] = this.pcapService.submitRequest(pcapRequest).subscribe( + (submitResponse: PcapStatusResponse) => { + let id = submitResponse.jobId; + if (!id) { + this.errorMsg = submitResponse.description; + this.queryRunning = false; + } else { + this.startPolling(id); + } + }, (error: any) => { + this.errorMsg = `Response message: ${error.message}. Something went wrong with your query submission!`; } - }, (error: any) => { - this.errorMsg = `Response message: ${error.message}. Something went wrong with your query submission!`; - }); + ); } startPolling(id: string) { this.queryId = id; this.errorMsg = null; - this.statusSubscription = this.pcapService.pollStatus(id).subscribe((statusResponse: PcapStatusResponse) => { + this.subscriptions['statusSubscription'] = this.pcapService.pollStatus(id).subscribe((statusResponse: PcapStatusResponse) => { this.updateStatus(statusResponse); }, (error: any) => { - this.statusSubscription.unsubscribe(); + this.subscriptions['statusSubscription'].unsubscribe(); this.queryRunning = false; this.errorMsg = `Response message: ${error.message}. Something went wrong with your status request!`; }); @@ -109,9 +111,9 @@ export class PcapPanelComponent implements OnInit, OnDestroy { updateStatus(statusResponse: PcapStatusResponse) { if ('SUCCEEDED' === statusResponse.jobStatus) { this.pagination.total = statusResponse.pageTotal; - this.statusSubscription.unsubscribe(); + this.subscriptions['statusSubscription'].unsubscribe(); this.queryRunning = false; - this.pcapService.getPackets(this.queryId, this.pagination.selectedPage).toPromise().then(pdml => { + this.subscriptions['packetSubscription'] = this.pcapService.getPackets(this.queryId, this.pagination.selectedPage).subscribe(pdml => { this.pdml = pdml; }, (error: RestError) => { if (error.status === 404) { @@ -121,7 +123,7 @@ export class PcapPanelComponent implements OnInit, OnDestroy { } }); } else if ('FAILED' === statusResponse.jobStatus) { - this.statusSubscription.unsubscribe(); + this.subscriptions['statusSubscription'].unsubscribe(); this.queryRunning = false; this.errorMsg = `Query status: ${statusResponse.jobStatus}. Check your filter criteria and try again!`; } else if (this.progressWidth < 100) { @@ -134,25 +136,19 @@ export class PcapPanelComponent implements OnInit, OnDestroy { } unsubscribeAll() { - if (this.cancelSubscription) { - this.cancelSubscription.unsubscribe(); - } - if (this.statusSubscription) { - this.statusSubscription.unsubscribe(); - } - if (this.submitSubscription) { - this.submitSubscription.unsubscribe(); - } + Object.keys(this.subscriptions).forEach((key) => { + this.subscriptions[key].unsubscribe(); + }); + this.subscriptions = {}; } cancelQuery() { - this.cancelSubscription = this.pcapService.cancelQuery(this.queryId) + this.subscriptions['cancelSubscription'] = this.pcapService.cancelQuery(this.queryId) .subscribe(() => { this.unsubscribeAll(); this.queryId = ''; this.queryRunning = false; }, (error: any) => { - this.cancelSubscription.unsubscribe(); this.queryId = ''; this.errorMsg = `Response message: ${error.message}. Something went wrong with the cancellation!`; this.queryRunning = false; http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/service/dialog.service.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/service/dialog.service.spec.ts b/metron-interface/metron-alerts/src/app/service/dialog.service.spec.ts new file mode 100644 index 0000000..521d74c --- /dev/null +++ b/metron-interface/metron-alerts/src/app/service/dialog.service.spec.ts @@ -0,0 +1,78 @@ +/** + * 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, inject } from '@angular/core/testing'; + +import { + DialogService, + DialogParams +} from './dialog.service'; +import { DialogType } from '../model/dialog-type'; +import { ConfirmationType } from '../model/confirmation-type'; + +describe('DialogService', () => { + let dialogService: DialogService; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [DialogService] + }); + dialogService = TestBed.get(DialogService); + }); + + it('should be created', inject([DialogService], (service: DialogService) => { + expect(service).toBeTruthy(); + })); + + describe('confirm()', () => { + + it('should emit a message with the correct params', () => { + const messageSpy = spyOn(dialogService.message, 'next'); + const testMessage = 'this is a test'; + let messageEmit: DialogParams = { + message: testMessage, + show: true, + dialogType: DialogType.Confirmation + }; + + dialogService.launchDialog(testMessage); + expect(messageSpy).toHaveBeenCalledWith(messageEmit); + + messageEmit.dialogType = DialogType.Error; + dialogService.launchDialog(testMessage, DialogType.Error); + }); + + }); + + describe('cancel()', () => { + it('should emit ConfirmationType.Rejected', () => { + const messageSpy = spyOn(dialogService.confirmed, 'next'); + + dialogService.cancel(); + expect(messageSpy).toHaveBeenCalledWith(ConfirmationType.Rejected); + }); + }); + + describe('approve()', () => { + it('should emit ConfirmationType.Confirmed', () => { + const messageSpy = spyOn(dialogService.confirmed, 'next'); + + dialogService.approve(); + expect(messageSpy).toHaveBeenCalledWith(ConfirmationType.Confirmed); + }); + }); +}); http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/service/dialog.service.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/service/dialog.service.ts b/metron-interface/metron-alerts/src/app/service/dialog.service.ts new file mode 100644 index 0000000..ad758a0 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/service/dialog.service.ts @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { ConfirmationType } from '../model/confirmation-type'; +import { DialogType } from '../model/dialog-type'; + +@Injectable({ + providedIn: 'root' +}) + +export class DialogParams { + show = false; + message = ''; + dialogType = DialogType.Confirmation; +} + +export class DialogService { + message = new BehaviorSubject<DialogParams>(new DialogParams()); + confirmed = new Subject<ConfirmationType>(); + + constructor() {} + + launchDialog(message: string, dialogType = DialogType.Confirmation): Subject<ConfirmationType> { + this.message.next({ + message: message, + show: true, + dialogType: dialogType + }); + return this.confirmed; + } + + approve() { + this.confirmed.next(ConfirmationType.Confirmed); + } + + cancel() { + this.confirmed.next(ConfirmationType.Rejected); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts b/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts deleted file mode 100644 index aaa2791..0000000 --- a/metron-interface/metron-alerts/src/app/shared/metron-dialog-box.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {EventEmitter} from '@angular/core'; - -declare var $; - -export enum DialogType { - Confirmation, Error -}; - -export class MetronDialogBox { - private static dialogType = DialogType; - - private getCancelButton(type: DialogType): string { - if (type === DialogType.Confirmation) { - return `<button type="button" class="btn btn-mine_shaft_2" data-dismiss="modal">Cancel</button>`; - } - - return ''; - } - - private createDialogBox(message: string, type: DialogType) { - let cancelButtonHTML = this.getCancelButton(type); - let html = `<div class="metron-dialog modal" data-backdrop="static" > - <div class="modal-dialog modal-sm" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <span class="modal-title"><b>` + MetronDialogBox.dialogType[type] + `</b></span> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - </div> - <div class="modal-body"> - <p>` + message + `</p> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-all_ports">OK</button>` - + cancelButtonHTML + - `</div> - </div> - </div> - </div>`; - - let element = document.createElement('div'); - element.innerHTML = html; - - document.body.appendChild(element); - - return element; - } - - public showConfirmationMessage(message: string, dialogType = DialogType.Confirmation): EventEmitter<boolean> { - message = message.replace(/\n/g, '<br>'); - let eventEmitter = new EventEmitter<boolean>(); - let element = this.createDialogBox(message, dialogType); - - $(element).find('.metron-dialog').modal('show'); - - $(element).find('.btn-all_ports').on('click', function (e) { - $(element).find('.metron-dialog').modal('hide'); - eventEmitter.emit(true); - }); - - $(element).find('.btn-mine_shaft_2').on('click', function (e) { - $(element).find('.metron-dialog').modal('hide'); - eventEmitter.emit(false); - }); - - $(element).find('.metron-dialog').on('hidden.bs.modal', function (e) { - $(element).remove(); - }); - - return eventEmitter; - - } -} http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.html ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.html b/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.html new file mode 100644 index 0000000..0308e97 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.html @@ -0,0 +1,40 @@ +<!-- + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +--> +<div> + <div *ngIf="showModal" class="metron-dialog modal show" [class.block]="showModal" data-backdrop="static" data-qe-id="modal"> + <div class="modal-dialog modal-sm" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <span class="modal-title" data-qe-id="modal-title"><b>{{ dialogType }}</b></span> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true" (click)="cancel()">×</span> + </button> + </div> + <div class="modal-body"> + <p data-qe-id="modal-message">{{ message }}</p> + </div> + <div class="modal-footer"> + <button *ngIf="dialogType === 'Error'" type="button" class="btn btn-all_ports" (click)="cancel()" data-qe-id="modal-error-cancel">OK</button> + <button *ngIf="dialogType !== 'Error'" type="button" class="btn btn-all_ports" (click)="confirm()" data-qe-id="modal-confirm">OK</button> + <button *ngIf="dialogType !== 'Error'" type="button" class="btn btn-mine_shaft_2" data-dismiss="modal" (click)="cancel()" data-qe-id="modal-cancel">Cancel</button> + </div> + </div> + </div> + </div> + <div class="modal-backdrop show" *ngIf="showModal"></div> +</div> http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.scss ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.scss b/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.scss new file mode 100644 index 0000000..675242b --- /dev/null +++ b/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.scss @@ -0,0 +1,20 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + .block { + display: block; +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.spec.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.spec.ts b/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.spec.ts new file mode 100644 index 0000000..c5540a6 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.spec.ts @@ -0,0 +1,127 @@ +/** + * 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 { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MetronDialogComponent } from './metron-dialog.component'; +import { DialogService } from 'app/service/dialog.service'; +import { DialogType } from 'app/model/dialog-type'; + +describe('MetronDialogComponent', () => { + let component: MetronDialogComponent; + let fixture: ComponentFixture<MetronDialogComponent>; + let dialogService: DialogService; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [MetronDialogComponent], + providers: [DialogService] + }); + fixture = TestBed.createComponent(MetronDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + dialogService = TestBed.get(DialogService); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should show when showModal is true', () => { + component.showModal = false; + fixture.detectChanges(); + let modal = fixture.nativeElement.querySelector('[data-qe-id="modal"]'); + expect(modal).toBeNull(); + + component.showModal = true; + fixture.detectChanges(); + modal = fixture.nativeElement.querySelector('[data-qe-id="modal"]'); + expect(modal).toBeTruthy(); + }); + + it('should display the passed message', () => { + const testMessage = 'This is a confirmation message'; + dialogService.launchDialog(testMessage); + fixture.detectChanges(); + const modalMessage = fixture.nativeElement.querySelector( + '[data-qe-id="modal-message"]' + ); + expect(modalMessage.textContent).toBe(testMessage); + }); + + it('should display the correct title based on the dialog type', () => { + dialogService.launchDialog(''); + fixture.detectChanges(); + let modalTitle = fixture.nativeElement.querySelector( + '[data-qe-id="modal-title"]' + ); + expect(modalTitle.textContent).toBe('Confirmation'); + + dialogService.launchDialog('', DialogType.Error); + fixture.detectChanges(); + modalTitle = fixture.nativeElement.querySelector( + '[data-qe-id="modal-title"]' + ); + expect(modalTitle.textContent).toBe('Error'); + }); + + it('should execute cancel() when the cancel button is clicked', () => { + dialogService.launchDialog(''); + const cancelSpy = spyOn(component, 'cancel').and.callThrough(); + fixture.detectChanges(); + + const cancelButton = fixture.nativeElement.querySelector( + '[data-qe-id="modal-cancel"]' + ); + cancelButton.click(); + expect(cancelSpy).toHaveBeenCalled(); + expect(component.showModal).toBe(false); + }); + + it('should execute confirm() when the ok button is clicked', () => { + dialogService.launchDialog(''); + const confirmSpy = spyOn(component, 'confirm').and.callThrough(); + fixture.detectChanges(); + + const confirmButton = fixture.nativeElement.querySelector( + '[data-qe-id="modal-confirm"]' + ); + confirmButton.click(); + expect(confirmSpy).toHaveBeenCalled(); + expect(component.showModal).toBe(false); + }); + + it('should only display a cancel() button when the dialog type is Error', () => { + dialogService.launchDialog('', DialogType.Error); + fixture.detectChanges(); + + const errorCancelButton = fixture.nativeElement.querySelector( + '[data-qe-id="modal-error-cancel"]' + ); + expect(errorCancelButton).toBeTruthy(); + + const confirmCancelButton = fixture.nativeElement.querySelector( + '[data-qe-id="modal-cancel"]' + ); + expect(confirmCancelButton).toBeNull(); + + const confirmApproveButton = fixture.nativeElement.querySelector( + '[data-qe-id="modal-confirm"]' + ); + expect(confirmApproveButton).toBeNull(); + }); +}); http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.ts ---------------------------------------------------------------------- diff --git a/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.ts b/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.ts new file mode 100644 index 0000000..7ba71e5 --- /dev/null +++ b/metron-interface/metron-alerts/src/app/shared/metron-dialog/metron-dialog.component.ts @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Component, + OnInit, + OnDestroy +} from '@angular/core'; +import { DialogService } from '../../service/dialog.service'; +import { Subscription } from 'rxjs'; + +export enum DialogType { + Confirmation, + Error +} + +@Component({ + selector: 'app-metron-dialog', + templateUrl: './metron-dialog.component.html', + styleUrls: ['./metron-dialog.component.scss'] +}) +export class MetronDialogComponent implements OnInit, OnDestroy { + public message: string; + public showModal = false; + public dialogType: string; + private subscription: Subscription; + + constructor(private dialogService: DialogService) {} + + ngOnInit(): void { + this.subscription = this.dialogService.message.subscribe(r => { + this.showModal = r.show; + this.message = r.message; + this.dialogType = DialogType[r.dialogType]; + }); + + } + + confirm(): void { + this.dialogService.approve(); + this.showModal = false; + } + + cancel(): void { + this.dialogService.cancel(); + this.showModal = false; + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } +} http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/angular-cli.json ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/angular-cli.json b/metron-interface/metron-config/angular-cli.json deleted file mode 100644 index 7e99b78..0000000 --- a/metron-interface/metron-config/angular-cli.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "project": { - "version": "1.0.0-beta.15", - "name": "metron-config" - }, - "apps": [ - { - "root": "src", - "outDir": "dist", - "assets": "assets", - "index": "index.html", - "main": "main.ts", - "test": "test.ts", - "tsconfig": "tsconfig.json", - "prefix": "metron-config", - "mobile": false, - "styles": [ - "../node_modules/bootstrap/dist/css/bootstrap.css", - "../node_modules/font-awesome/css/font-awesome.css", - "styles.scss" - ], - "scripts": [ - "../node_modules/jquery/dist/jquery.js", - "../node_modules/tether/dist/js/tether.js", - "../node_modules/bootstrap/dist/js/bootstrap.js", - "../node_modules/ace-builds/src-noconflict/ace.js" - ], - "environments": { - "source": "environments/environment.ts", - "dev": "environments/environment.ts", - "prod": "environments/environment.prod.ts" - } - } - ], - "addons": [], - "packages": [], - "e2e": { - "protractor": { - "config": "./protractor.conf.js" - } - }, - "test": { - "karma": { - "config": "./karma.conf.js" - } - }, - "defaults": { - "styleExt": "scss", - "prefixInterfaces": false - } -} http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/angular.json ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/angular.json b/metron-interface/metron-config/angular.json new file mode 100644 index 0000000..06c4596 --- /dev/null +++ b/metron-interface/metron-config/angular.json @@ -0,0 +1,155 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "metron-config": { + "root": "", + "sourceRoot": "src", + "projectType": "application", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist", + "index": "src/index.html", + "main": "src/main.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + "src/assets" + ], + "styles": [ + "node_modules/bootstrap/dist/css/bootstrap.css", + "node_modules/font-awesome/css/font-awesome.css", + "src/styles.scss" + ], + "scripts": [ + "node_modules/jquery/dist/jquery.js", + "node_modules/tether/dist/js/tether.js", + "node_modules/bootstrap/dist/js/bootstrap.js", + "node_modules/ace-builds/src-noconflict/ace.js" + ] + }, + "configurations": { + "production": { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ] + }, + "e2e": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.e2e.ts" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "metron-config:build" + }, + "configurations": { + "source": { + "browserTarget": "metron-config:build:source" + }, + "dev": { + "browserTarget": "metron-config:build:dev" + }, + "production": { + "browserTarget": "metron-config:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "metron-config:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "karmaConfig": "./karma.conf.js", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "scripts": [ + "node_modules/jquery/dist/jquery.js", + "node_modules/tether/dist/js/tether.js", + "node_modules/bootstrap/dist/js/bootstrap.js", + "node_modules/ace-builds/src-noconflict/ace.js" + ], + "styles": [ + "node_modules/bootstrap/dist/css/bootstrap.css", + "node_modules/font-awesome/css/font-awesome.css", + "src/vendor.scss", + "src/styles.scss" + ], + "assets": [ + "src/assets", + "src/favicon.ico" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [] + } + } + } + }, + "metron-config-e2e": { + "root": "e2e", + "sourceRoot": "e2e", + "projectType": "application", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "./protractor.conf.js", + "devServerTarget": "metron-config:serve" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "e2e/tsconfig.e2e.json" + ], + "exclude": [] + } + } + } + } + }, + "defaultProject": "metron-config", + "schematics": { + "@schematics/angular:component": { + "prefix": "metron-config", + "styleext": "scss" + }, + "@schematics/angular:directive": { + "prefix": "metron-config" + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/metron/blob/8bf3b6ec/metron-interface/metron-config/karma.conf.js ---------------------------------------------------------------------- diff --git a/metron-interface/metron-config/karma.conf.js b/metron-interface/metron-config/karma.conf.js index 6023550..aa71b96 100644 --- a/metron-interface/metron-config/karma.conf.js +++ b/metron-interface/metron-config/karma.conf.js @@ -15,50 +15,56 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - // Karma configuration file, see link for more information // https://karma-runner.github.io/0.13/config/configuration-file.html +process.env.CHROME_BIN = require('puppeteer').executablePath() + module.exports = function (config) { config.set({ basePath: '', - frameworks: ['jasmine', 'angular-cli'], + frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), - require('karma-remap-istanbul'), - require('karma-phantomjs-launcher'), - require('angular-cli/plugins/karma') + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular-devkit/build-angular/plugins/karma') ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, mime: { 'text/x-typescript': ['ts','tsx'] }, - files: [ - { pattern: './src/test.ts', watched: false }, - { pattern: './src/assets/**', watched: false, included: false, nocache: false, served: true } - ], - proxies: { - '/assets': '/base/src/assets/' - }, - preprocessors: { - './src/test.ts': ['angular-cli'] + coverageIstanbulReporter: { + dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly', 'text-summary' ], + fixWebpackSourcePaths: true }, - remapIstanbulReporter: { - reports: { - html: 'coverage', - lcovonly: './coverage/coverage.lcov' - } - }, - angularCli: { - config: './angular-cli.json', - environment: 'dev' - }, - reporters: ['progress', 'karma-remap-istanbul'], + captureTimeout: 30000, + browserDisconnectTolerance: 3, + browserDisconnectTimeout : 30000, + browserNoActivityTimeout : 30000, + + reporters: config.angularCli && config.angularCli.codeCoverage + ? ['progress', 'coverage-istanbul'] + : ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, - browsers: ['PhantomJS'], + browsers: ['Chrome','ChromeHeadless'], + customLaunchers: { + ChromeHeadless: { + base: 'Chrome', + flags: [ + '--no-sandbox', + '--headless', + '--disable-gpu', + '--remote-debugging-port=9222' + ] + } + }, singleRun: false }); -}; +}; \ No newline at end of file