diff --git a/package.json b/package.json
index 127fbdd40..5258c01af 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "gns3-web-ui",
- "version": "2.2.46",
+ "version": "2.2.47",
"author": {
"name": "GNS3 Technology Inc.",
"email": "developers@gns3.com"
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 1769c4db8..dcc6ac461 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/cartography/models/node.ts b/src/app/cartography/models/node.ts
index 8bef9b6a3..c422e428e 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 15f3f924a..6faeccf95 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 57f5df01d..1f01294aa 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,41 +16,16 @@ 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() {
+ openConsole(auxiliary: boolean = false) {
this.nodeService.getNode(this.server, 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");
- }
-
- 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 {
@@ -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;
@@ -69,7 +44,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')) {
@@ -79,15 +65,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;
}
- 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 1d7886c32..bc6cc8b5b 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 594122102..1c3ed625e 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 ebb8a6445..735715156 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 3f2ce336b..3dfd32c3f 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 = ``;
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 642a3d72e..d3ebb26ee 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 000000000..70ca8384f
--- /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);
+ }
+ }
+}