diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 12136074..b1f523be 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -217,6 +217,7 @@ import { DefaultLayoutComponent } from './layouts/default-layout/default-layout. import { MATERIAL_IMPORTS } from './material.imports'; import { ControllerResolve } from './resolvers/controller-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'; @@ -651,6 +652,7 @@ import { DeleteResourceConfirmationDialogComponent } from './components/resource InfoService, ComputeService, PacketCaptureService, + ProtocolHandlerService, NotificationService, ThemeService, GoogleAnalyticsService, diff --git a/src/app/cartography/models/node.ts b/src/app/cartography/models/node.ts index f3800453..7ef7b21d 100644 --- a/src/app/cartography/models/node.ts +++ b/src/app/cartography/models/node.ts @@ -14,6 +14,7 @@ export class Properties { headless: boolean; linked_clone: boolean; on_close: string; + aux: number; ram: number; nvram: number; usage: string; diff --git a/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.html b/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.html index 15f3f924..6faeccf9 100644 --- a/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.html +++ b/src/app/components/project-map/context-menu/actions/console-device-action-browser/console-device-action-browser.component.html @@ -2,3 +2,11 @@ web_asset Console + 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 194e2526..d0da55c0 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 { Controller } from '../../../../../models/controller'; 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({ @@ -17,43 +19,18 @@ export class ConsoleDeviceActionBrowserComponent { constructor( private toasterService: ToasterService, private nodeService: NodeService, - private deviceService: DeviceDetectorService + private deviceService: DeviceDetectorService, + private protocolHandlerService: ProtocolHandlerService ) {} - openConsole() { + openConsole(auxiliary: boolean = false) { this.nodeService.getNode(this.controller, this.node).subscribe((node: Node) => { this.node = node; - this.startConsole(); + this.startConsole(auxiliary); }); } - 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"); - //setTimeout(() => { iframe.parentNode.removeChild(iframe); }, 0); - } - - try { - iframe.contentWindow.location.href = uri; - } catch (e) { - if (e.name === "NS_ERROR_UNKNOWN_PROTOCOL") { - this.toasterService.error('Protocol handler does not exist'); - } - } - } - - startConsole() { + startConsole(auxiliary: boolean) { if (this.node.status !== 'started') { this.toasterService.error('This node must be started before a console can be opened'); } else { @@ -65,8 +42,6 @@ export class ConsoleDeviceActionBrowserComponent { this.node.console_host = this.controller.host; } - const device = this.deviceService.getDeviceInfo(); - try { var uri; var host = this.node.console_host; @@ -74,7 +49,18 @@ export class ConsoleDeviceActionBrowserComponent { host = `[${host}]`; } if (this.node.console_type === 'telnet') { - uri = `gns3+telnet://${host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`; + + var console_port; + if (auxiliary === true) { + console_port = this.node.properties.aux; + if (console_port === undefined) { + this.toasterService.error('Auxiliary console port is not set.'); + return; + } + } else { + console_port = this.node.console; + } + uri = `gns3+telnet://${host}:${console_port}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`; } else if (this.node.console_type === 'vnc') { uri = `gns3+vnc://${host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`; } else if (this.node.console_type.startsWith('spice')) { @@ -84,16 +70,10 @@ export class ConsoleDeviceActionBrowserComponent { return window.open(uri); // open an http console directly in a new window/tab } else { this.toasterService.error('Supported console types are: telnet, vnc, spice and spice+agent.'); + return; } - console.log("Opening external console using " + device.browser + " browser"); - 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 ff2dc462..22e0126b 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 httpController = new HttpController({} as HttpClient, {} as ControllerErrorHandler); @@ -52,6 +54,7 @@ describe('LogConsoleComponent', () => { { provide: HttpController, useValue: httpController }, 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 9ed2660b..5bb74afb 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{ Controller } from '../../../models/controller'; import { HttpController } from '../../../services/http-controller.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: HttpController, 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 69bdd22b..b3a01cba 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,11 +52,14 @@ 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(() => { fixture = TestBed.createComponent(StartCaptureDialogComponent); diff --git a/src/app/services/packet-capture.service.ts b/src/app/services/packet-capture.service.ts index 83eae678..ffe3a3d8 100644 --- a/src/app/services/packet-capture.service.ts +++ b/src/app/services/packet-capture.service.ts @@ -1,15 +1,18 @@ import { Injectable } from '@angular/core'; import { Link } from '../models/link'; import { Project } from '../models/project'; -import{ Controller } from '../models/controller'; +import { Controller } from '../models/controller'; +import { ProtocolHandlerService } from './protocol-handler.service'; @Injectable() export class PacketCaptureService { - constructor() {} - startCapture(controller:Controller , project: Project, link: Link, name: string) { - location.assign( - `gns3+pcap://${controller.host}:${controller.port}?protocol=${controller.protocol.slice(0, -1)}&project_id=${project.project_id}&link_id=${link.link_id}&project=${project.name}&name=${name}` - ); + constructor(private protocolHandlerService: ProtocolHandlerService) {} + + startCapture(controller: Controller, project: Project, link: Link, name: string) { + + const uri = `gns3+pcap://${controller.host}:${controller.port}?protocol=${controller.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); + } + } +}