diff --git a/src/app/cartography/events/event-source.ts b/src/app/cartography/events/event-source.ts index a54e4d5d..7252129a 100644 --- a/src/app/cartography/events/event-source.ts +++ b/src/app/cartography/events/event-source.ts @@ -1,5 +1,6 @@ import { TextElement } from '../models/drawings/text-element'; import { MapDrawing } from '../models/map/map-drawing'; +import { MapLink } from '../models/map/map-link'; export class DataEventSource { constructor(public datum: T, public dx: number, public dy: number) {} @@ -30,3 +31,7 @@ export class TextEditedDataEvent { export class DrawingContextMenu { constructor(public event: any, public drawing: MapDrawing) {} } + +export class LinkContextMenu { + constructor(public event:any, public link: MapLink) {} +} diff --git a/src/app/cartography/widgets/link.ts b/src/app/cartography/widgets/link.ts index 1944e5c6..19438bc0 100644 --- a/src/app/cartography/widgets/link.ts +++ b/src/app/cartography/widgets/link.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, EventEmitter } from '@angular/core'; import { Widget } from './widget'; import { SVGSelection } from '../models/types'; @@ -9,9 +9,12 @@ import { InterfaceLabelWidget } from './interface-label'; import { InterfaceStatusWidget } from './interface-status'; import { MapLink } from '../models/map/map-link'; import { SelectionManager } from '../managers/selection-manager'; +import { LinkContextMenu } from '../events/event-source'; @Injectable() export class LinkWidget implements Widget { + public onContextMenu = new EventEmitter(); + constructor( private multiLinkCalculatorHelper: MultiLinkCalculatorHelper, private interfaceLabelWidget: InterfaceLabelWidget, @@ -36,6 +39,10 @@ export class LinkWidget implements Widget { link_body .filter(l => { return l.capturing && !(l.filters.bpf || l.filters.corrupt || l.filters.delay || l.filters.frequency_drop || l.filters.packet_loss)}) .append('g') + .on('contextmenu', (datum: MapLink) => { + event.preventDefault(); + this.onContextMenu.emit(new LinkContextMenu(event, datum)); + }) .attr('class', 'capture-icon') .attr('transform', link => { return `translate (${(link.source.x + link.target.x)/2}, ${(link.source.y + link.target.y)/2}) scale(0.5)` @@ -48,6 +55,10 @@ export class LinkWidget implements Widget { link_body .filter(l => { return l.capturing && (l.filters.bpf || l.filters.corrupt || l.filters.delay || l.filters.frequency_drop || l.filters.packet_loss)}) .append('g') + .on('contextmenu', (datum: MapLink) => { + event.preventDefault(); + this.onContextMenu.emit(new LinkContextMenu(event, datum)); + }) .attr('class', 'filter-capture-icon') .attr('transform', link => { return `translate (${(link.source.x + link.target.x)/2}, ${(link.source.y + link.target.y)/2}) scale(0.5)` @@ -60,6 +71,10 @@ export class LinkWidget implements Widget { link_body .filter(l => { return !l.capturing && (l.filters.bpf || l.filters.corrupt || l.filters.delay || l.filters.frequency_drop || l.filters.packet_loss)}) .append('g') + .on('contextmenu', (datum: MapLink) => { + event.preventDefault(); + this.onContextMenu.emit(new LinkContextMenu(event, datum)); + }) .attr('class', 'filter-icon') .attr('transform', link => { return `translate (${(link.source.x + link.target.x)/2}, ${(link.source.y + link.target.y)/2}) scale(0.5)` diff --git a/src/app/components/project-map/packet-capturing/start-capture/start-capture.component.spec.ts b/src/app/components/project-map/packet-capturing/start-capture/start-capture.component.spec.ts index 22d38041..793880ee 100644 --- a/src/app/components/project-map/packet-capturing/start-capture/start-capture.component.spec.ts +++ b/src/app/components/project-map/packet-capturing/start-capture/start-capture.component.spec.ts @@ -8,9 +8,10 @@ import { ToasterService } from '../../../../services/toaster.service'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { MockedToasterService } from '../../../../services/toaster.service.spec'; import { LinkService } from '../../../../services/link.service'; -import { MockedLinkService } from '../../project-map.component.spec'; +import { MockedLinkService, MockedNodesDataSource } from '../../project-map.component.spec'; import { Link } from '../../../../models/link'; import { of } from 'rxjs'; +import { NodesDataSource } from '../../../../cartography/datasources/nodes-datasource'; describe('StartCaptureDialogComponent', () => { let component: StartCaptureDialogComponent; @@ -18,6 +19,7 @@ describe('StartCaptureDialogComponent', () => { let mockedToasterService = new MockedToasterService; let mockedLinkService = new MockedLinkService; + let mockedNodesDataSource = new MockedNodesDataSource; let dialogRef = { close: jasmine.createSpy('close') }; @@ -29,7 +31,8 @@ describe('StartCaptureDialogComponent', () => { { provide: MatDialogRef, useValue: dialogRef }, { provide: MAT_DIALOG_DATA, useValue: [] }, { provide: ToasterService, useValue: mockedToasterService }, - { provide: LinkService, useValue: mockedLinkService } + { provide: LinkService, useValue: mockedLinkService }, + { provide: NodesDataSource, useValue: mockedNodesDataSource } ], declarations: [ StartCaptureDialogComponent @@ -41,7 +44,7 @@ describe('StartCaptureDialogComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(StartCaptureDialogComponent); component = fixture.componentInstance; - component.link = {link_type: 'ethernet'} as Link; + component.link = {link_type: 'ethernet', nodes: [{node_id: '1'}]} as Link; fixture.detectChanges(); }); @@ -68,7 +71,7 @@ describe('StartCaptureDialogComponent', () => { expect(mockedLinkService.startCaptureOnLink).not.toHaveBeenCalled(); }); - it('should call link service when filename is empty', () => { + it('should not call link service when filename is empty', () => { component.inputForm.controls['linkType'].setValue('Ethernet'); component.inputForm.controls['fileName'].setValue(''); spyOn(mockedLinkService, 'startCaptureOnLink').and.returnValue(of({})); diff --git a/src/app/components/project-map/packet-capturing/start-capture/start-capture.component.ts b/src/app/components/project-map/packet-capturing/start-capture/start-capture.component.ts index 62954865..72e8aaf9 100644 --- a/src/app/components/project-map/packet-capturing/start-capture/start-capture.component.ts +++ b/src/app/components/project-map/packet-capturing/start-capture/start-capture.component.ts @@ -7,6 +7,8 @@ import { LinkService } from '../../../../services/link.service'; import { CapturingSettings } from '../../../../models/capturingSettings'; import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; import { ToasterService } from '../../../../services/toaster.service'; +import { LinkNode } from '../../../../models/link-node'; +import { NodesDataSource } from '../../../../cartography/datasources/nodes-datasource'; @Component({ selector: 'app-start-capture', @@ -23,7 +25,8 @@ export class StartCaptureDialogComponent implements OnInit { private dialogRef: MatDialogRef, private linkService: LinkService, private formBuilder: FormBuilder, - private toasterService: ToasterService + private toasterService: ToasterService, + private nodesDataSource: NodesDataSource ) { this.inputForm = this.formBuilder.group({ linkType: new FormControl('', Validators.required), @@ -47,7 +50,15 @@ export class StartCaptureDialogComponent implements OnInit { } onYesClick() { - if (this.inputForm.invalid) { + let isAnyRunningDevice = false; + this.link.nodes.forEach((linkNode: LinkNode) => { + let node = this.nodesDataSource.get(linkNode.node_id); + if (node.status === 'started') isAnyRunningDevice = true; + }); + + if (!isAnyRunningDevice) { + this.toasterService.error(`Cannot capture because there is no running device on this link`); + } else if (this.inputForm.invalid) { this.toasterService.error(`Fill all required fields`); } else { let captureSettings: CapturingSettings = { diff --git a/src/app/components/project-map/project-map.component.spec.ts b/src/app/components/project-map/project-map.component.spec.ts index 28584421..86b10868 100644 --- a/src/app/components/project-map/project-map.component.spec.ts +++ b/src/app/components/project-map/project-map.component.spec.ts @@ -41,6 +41,7 @@ import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-lin import { Link } from '../../models/link'; import { Project } from '../../models/project'; import { CapturingSettings } from '../../models/capturingSettings'; +import { LinkWidget } from '../../cartography/widgets/link'; export class MockedProgressService { public activate() {} @@ -160,7 +161,7 @@ export class MockedNodesDataSource { clear() {} get() { - return of({}); + return {status: 'started'}; } update() { @@ -194,6 +195,7 @@ describe('ProjectMapComponent', () => { { provide: ProjectWebServiceHandler }, { provide: MapChangeDetectorRef }, { provide: NodeWidget }, + { provide: LinkWidget }, { provide: DrawingsWidget }, { provide: MapNodeToNodeConverter }, { provide: MapDrawingToDrawingConverter }, diff --git a/src/app/components/project-map/project-map.component.ts b/src/app/components/project-map/project-map.component.ts index 05b15f80..0681ef01 100644 --- a/src/app/components/project-map/project-map.component.ts +++ b/src/app/components/project-map/project-map.component.ts @@ -30,7 +30,7 @@ import { MapNodeToNodeConverter } from '../../cartography/converters/map/map-nod import { SettingsService, Settings } from '../../services/settings.service'; import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component'; import { ToolsService } from '../../services/tools.service'; -import { DrawingContextMenu } from '../../cartography/events/event-source'; +import { DrawingContextMenu, LinkContextMenu } from '../../cartography/events/event-source'; import { MapDrawingToDrawingConverter } from '../../cartography/converters/map/map-drawing-to-drawing-converter'; import { SelectionManager } from '../../cartography/managers/selection-manager'; import { SelectionTool } from '../../cartography/tools/selection-tool'; @@ -42,6 +42,7 @@ import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-l import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service'; import { MapLink } from '../../cartography/models/map/map-link'; import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter'; +import { LinkWidget } from '../../cartography/widgets/link'; @Component({ @@ -95,6 +96,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private mapChangeDetectorRef: MapChangeDetectorRef, private nodeWidget: NodeWidget, private drawingsWidget: DrawingsWidget, + private linkWidget: LinkWidget, private mapNodeToNode: MapNodeToNodeConverter, private mapDrawingToDrawing: MapDrawingToDrawingConverter, private mapLabelToLabel: MapLabelToLabelConverter, @@ -219,6 +221,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy { this.toolsService.selectionToolActivation(true); } + const onLinkContextMenu = this.linkWidget.onContextMenu.subscribe((eventLink: LinkContextMenu) => { + const link = this.mapLinkToLink.convert(eventLink.link); + this.contextMenu.openMenuForListOfElements([], [], [], [link], eventLink.event.pageY, eventLink.event.pageX); + }); + const onNodeContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => { const node = this.mapNodeToNode.convert(eventNode.node); this.contextMenu.openMenuForNode(node, eventNode.event.pageY, eventNode.event.pageX); @@ -253,6 +260,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { this.contextMenu.openMenuForListOfElements(drawings, nodes, labels, links, event.pageY, event.pageX); }); + this.subscriptions.push(onLinkContextMenu); this.subscriptions.push(onNodeContextMenu); this.subscriptions.push(onDrawingContextMenu); this.subscriptions.push(onContextMenu);