diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1769c4db..dcc6ac46 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -215,6 +215,7 @@ import { DefaultLayoutComponent } from './layouts/default-layout/default-layout. import { MATERIAL_IMPORTS } from './material.imports'; import { ServerResolve } from './resolvers/server-resolve'; import { ApplianceService } from './services/appliances.service'; +import { ProtocolHandlerService } from './services/protocol-handler.service'; import { BuiltInTemplatesConfigurationService } from './services/built-in-templates-configuration.service'; import { BuiltInTemplatesService } from './services/built-in-templates.service'; import { ComputeService } from './services/compute.service'; @@ -538,6 +539,7 @@ import { RotationValidator } from './validators/rotation-validator'; ComputeService, TracengService, PacketCaptureService, + ProtocolHandlerService, NotificationService, Gns3vmService, ThemeService, diff --git a/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.ts b/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.ts index 57f5df01..c521be41 100644 --- a/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.ts +++ b/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.ts @@ -4,6 +4,8 @@ import { Node } from '../../../../../cartography/models/node'; import { Server } from '../../../../../models/server'; import { NodeService } from '../../../../../services/node.service'; import { ToasterService } from '../../../../../services/toaster.service'; +import { ProtocolHandlerService } from '../../../../../services/protocol-handler.service'; + import * as ipaddr from 'ipaddr.js'; @Component({ @@ -14,7 +16,7 @@ export class ConsoleDeviceActionBrowserComponent { @Input() server: Server; @Input() node: Node; - constructor(private toasterService: ToasterService, private nodeService: NodeService, private deviceService: DeviceDetectorService) {} + constructor(private toasterService: ToasterService, private nodeService: NodeService, private protocolHandlerService: ProtocolHandlerService) {} openConsole() { this.nodeService.getNode(this.server, this.node).subscribe((node: Node) => { @@ -23,31 +25,6 @@ export class ConsoleDeviceActionBrowserComponent { }); } - createHiddenIframe(target: Element, uri: string) { - const iframe = document.createElement("iframe"); - iframe.src = uri; - iframe.id = "hiddenIframe"; - iframe.style.display = "none"; - target.appendChild(iframe); - return iframe; - } - - openUriUsingFirefox(uri: string) { - var iframe = (document.querySelector("#hiddenIframe") as HTMLIFrameElement); - - if (!iframe) { - iframe = this.createHiddenIframe(document.body, "about:blank"); - } - - try { - iframe.contentWindow.location.href = uri; - } catch (e) { - if (e.name === "NS_ERROR_UNKNOWN_PROTOCOL") { - this.toasterService.error('Protocol handler does not exist'); - } - } - } - startConsole() { if (this.node.status !== 'started') { this.toasterService.error('This node must be started before a console can be opened'); @@ -60,8 +37,6 @@ export class ConsoleDeviceActionBrowserComponent { this.node.console_host = this.server.host; } - const device = this.deviceService.getDeviceInfo(); - try { var uri; var host = this.node.console_host; @@ -81,13 +56,7 @@ export class ConsoleDeviceActionBrowserComponent { this.toasterService.error('Supported console types are: telnet, vnc, spice and spice+agent.'); } - if (device.browser === "Firefox") { - // Use a hidden iframe otherwise Firefox will disconnect - // from the GNS3 controller websocket if we use location.assign() - this.openUriUsingFirefox(uri); - } else { - location.assign(uri); - } + this.protocolHandlerService.open(uri); } catch (e) { this.toasterService.error(e); diff --git a/src/app/components/project-map/log-console/log-console.component.spec.ts b/src/app/components/project-map/log-console/log-console.component.spec.ts index 1d7886c3..bc6cc8b5 100644 --- a/src/app/components/project-map/log-console/log-console.component.spec.ts +++ b/src/app/components/project-map/log-console/log-console.component.spec.ts @@ -6,6 +6,7 @@ import { MatMenuModule } from '@angular/material/menu'; import { BrowserModule } from '@angular/platform-browser'; import { RouterTestingModule } from '@angular/router/testing'; import { ToasterService } from '../../../services/toaster.service'; +import { ProtocolHandlerService } from '../../../services/protocol-handler.service'; import { of } from 'rxjs'; import { NodesDataSource } from '../../../cartography/datasources/nodes-datasource'; import { ProjectWebServiceHandler, WebServiceMessage } from '../../../handlers/project-web-service-handler'; @@ -38,6 +39,7 @@ describe('LogConsoleComponent', () => { let nodeConsoleService: NodeConsoleService; let mapSettingsService: MapSettingsService; let toasterService: ToasterService; + let protocolHandlerService: ProtocolHandlerService; let httpServer = new HttpServer({} as HttpClient, {} as ServerErrorHandler); @@ -52,6 +54,7 @@ describe('LogConsoleComponent', () => { { provide: HttpServer, useValue: httpServer }, NodeConsoleService, ToasterService, + ProtocolHandlerService, MapSettingsService ], declarations: [LogConsoleComponent], @@ -59,6 +62,7 @@ describe('LogConsoleComponent', () => { }).compileComponents(); toasterService = TestBed.inject(ToasterService); + protocolHandlerService = TestBed.inject(ProtocolHandlerService); mapSettingsService = TestBed.inject(MapSettingsService); nodeConsoleService = TestBed.inject(NodeConsoleService); })); diff --git a/src/app/components/project-map/log-console/log-console.component.ts b/src/app/components/project-map/log-console/log-console.component.ts index 59412210..1c3ed625 100644 --- a/src/app/components/project-map/log-console/log-console.component.ts +++ b/src/app/components/project-map/log-console/log-console.component.ts @@ -23,6 +23,7 @@ import { Server } from '../../../models/server'; import { HttpServer } from '../../../services/http-server.service'; import { NodeService } from '../../../services/node.service'; import { NodeConsoleService } from '../../../services/nodeConsole.service'; +import { ProtocolHandlerService } from '../../../services/protocol-handler.service'; import { ThemeService } from '../../../services/theme.service'; import { version } from '../../../version'; import { LogEventsDataSource } from './log-events-datasource'; @@ -70,6 +71,7 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy { private projectWebServiceHandler: ProjectWebServiceHandler, private nodeService: NodeService, private nodesDataSource: NodesDataSource, + private protocolHandlerService: ProtocolHandlerService, private logEventsDataSource: LogEventsDataSource, private httpService: HttpServer, private themeService: ThemeService, @@ -230,15 +232,15 @@ export class LogConsoleComponent implements OnInit, AfterViewInit, OnDestroy { host = `[${host}]`; } if (node.console_type === 'telnet') { - location.assign( + this.protocolHandlerService.open( `gns3+telnet://${host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}` ); } else if (node.console_type === 'vnc') { - location.assign( + this.protocolHandlerService.open( `gns3+vnc://${host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}` ); } else if (node.console_type.startsWith('spice')) { - location.assign( + this.protocolHandlerService.open( `gns3+spice://${host}:${node.console}?name=${node.name}&project_id=${node.project_id}&node_id=${node.node_id}` ); } else if (node.console_type.startsWith('http')) { 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 ebb8a644..73571515 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 @@ -17,6 +17,7 @@ import { ToasterService } from '../../../../services/toaster.service'; import { MockedToasterService } from '../../../../services/toaster.service.spec'; import { MockedLinkService, MockedNodesDataSource } from '../../project-map.component.spec'; import { StartCaptureDialogComponent } from './start-capture.component'; +import { ProtocolHandlerService } from '../../../../services/protocol-handler.service'; describe('StartCaptureDialogComponent', () => { let component: StartCaptureDialogComponent; @@ -25,6 +26,8 @@ describe('StartCaptureDialogComponent', () => { let mockedToasterService = new MockedToasterService(); let mockedLinkService = new MockedLinkService(); let mockedNodesDataSource = new MockedNodesDataSource(); + let protocolHandlerService: ProtocolHandlerService; + let dialogRef = { close: jasmine.createSpy('close'), }; @@ -49,10 +52,13 @@ describe('StartCaptureDialogComponent', () => { { provide: LinkService, useValue: mockedLinkService }, { provide: NodesDataSource, useValue: mockedNodesDataSource }, { provide: PacketCaptureService }, + ProtocolHandlerService, ], declarations: [StartCaptureDialogComponent], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); + + protocolHandlerService = TestBed.inject(ProtocolHandlerService); })); beforeEach(() => { diff --git a/src/app/components/project-map/project-map.component.ts b/src/app/components/project-map/project-map.component.ts index 3f2ce336..3dfd32c3 100644 --- a/src/app/components/project-map/project-map.component.ts +++ b/src/app/components/project-map/project-map.component.ts @@ -67,6 +67,7 @@ import { SymbolService } from '../../services/symbol.service'; import { ThemeService } from '../../services/theme.service'; import { ToasterService } from '../../services/toaster.service'; import { ToolsService } from '../../services/tools.service'; +import { ProtocolHandlerService } from '../../services/protocol-handler.service'; import { AddBlankProjectDialogComponent } from '../projects/add-blank-project-dialog/add-blank-project-dialog.component'; import { ConfirmationBottomSheetComponent } from '../projects/confirmation-bottomsheet/confirmation-bottomsheet.component'; import { EditProjectDialogComponent } from '../projects/edit-project-dialog/edit-project-dialog.component'; @@ -173,8 +174,9 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private title: Title, private nodeConsoleService: NodeConsoleService, private symbolService: SymbolService, + private protocolHandlerService: ProtocolHandlerService, private cd: ChangeDetectorRef, - private cfr: ComponentFactoryResolver, + private cfr: ComponentFactoryResolver, private injector: Injector ) {} @@ -229,7 +231,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { this.instance.instance.ngOnDestroy(); this.instance.destroy(); } - } + } } addSubscriptions() { @@ -975,7 +977,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { ) { this.toasterService.error('Project with running nodes cannot be exported.'); } else { - location.assign(this.projectService.getExportPath(this.server, this.project)); + this.protocolHandlerService.open(this.projectService.getExportPath(this.server, this.project)); } } @@ -990,8 +992,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy { fileReader.onloadend = () => { let image = fileReader.result; - let svg = `\n\n\n`; this.drawingService .add(this.server, this.project.project_id, -(imageToUpload.width / 2), -(imageToUpload.height / 2), svg) diff --git a/src/app/services/packet-capture.service.ts b/src/app/services/packet-capture.service.ts index 642a3d72..d3ebb26e 100644 --- a/src/app/services/packet-capture.service.ts +++ b/src/app/services/packet-capture.service.ts @@ -2,14 +2,17 @@ import { Injectable } from '@angular/core'; import { Link } from '../models/link'; import { Project } from '../models/project'; import { Server } from '../models/server'; +import { ProtocolHandlerService } from './protocol-handler.service'; @Injectable() export class PacketCaptureService { - constructor() {} + + constructor(private protocolHandlerService: ProtocolHandlerService) {} startCapture(server: Server, project: Project, link: Link, name: string) { - location.assign( - `gns3+pcap://${server.host}:${server.port}?protocol=${server.protocol.slice(0, -1)}&project_id=${project.project_id}&link_id=${link.link_id}&project=${project.name}&name=${name}` - ); + + const uri = `gns3+pcap://${server.host}:${server.port}?protocol=${server.protocol.slice(0, -1)}&project_id=${project.project_id}&link_id=${link.link_id}&project=${project.name}&name=${name}`; + this.protocolHandlerService.open(uri); + } } diff --git a/src/app/services/protocol-handler.service.ts b/src/app/services/protocol-handler.service.ts new file mode 100644 index 00000000..70ca8384 --- /dev/null +++ b/src/app/services/protocol-handler.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; +import { DeviceDetectorService } from 'ngx-device-detector'; +import { ToasterService } from './toaster.service'; + +@Injectable() +export class ProtocolHandlerService { + + constructor(private toasterService: ToasterService, private deviceService: DeviceDetectorService) {} + + createHiddenIframe(target: Element, uri: string) { + const iframe = document.createElement("iframe"); + iframe.src = uri; + iframe.id = "hiddenIframe"; + iframe.style.display = "none"; + target.appendChild(iframe); + return iframe; + } + + openUriUsingFirefox(uri: string) { + var iframe = (document.querySelector("#hiddenIframe") as HTMLIFrameElement); + + if (!iframe) { + iframe = this.createHiddenIframe(document.body, "about:blank"); + } + + try { + iframe.contentWindow.location.href = uri; + } catch (e) { + if (e.name === "NS_ERROR_UNKNOWN_PROTOCOL") { + this.toasterService.error('Protocol handler does not exist'); + } + } + } + + open(uri: string) { + + const device = this.deviceService.getDeviceInfo(); + + console.log("Launching external protocol handler with " + device.browser + ": " + uri) + if (device.browser === "Firefox") { + // Use a hidden iframe otherwise Firefox will disconnect + // from the GNS3 controller websocket if we use location.assign() + this.openUriUsingFirefox(uri); + } else { + location.assign(uri); + } + } +}