mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-01-10 15:02:55 +00:00
Merge branch 'master' into master-2.3
This commit is contained in:
commit
0386d97a56
@ -1,5 +1,5 @@
|
||||
# Dockerfile for GNS3 Web-ui development
|
||||
FROM node:carbon
|
||||
FROM node:stretch
|
||||
|
||||
# Create user
|
||||
RUN useradd --user-group --create-home --shell /bin/false gns3-web-ui
|
||||
|
@ -6,6 +6,9 @@
|
||||
[![CircleCI](https://circleci.com/gh/GNS3/gns3-web-ui/tree/master.png)](https://circleci.com/gh/GNS3/gns3-web-ui/tree/master.png)
|
||||
[![codecov](https://codecov.io/gh/GNS3/gns3-web-ui/branch/master/graph/badge.svg)](https://codecov.io/gh/GNS3/gns3-web-ui)
|
||||
|
||||
[![Dependency](https://img.shields.io/librariesio/github/GNS3/gns3-web-ui)](https://libraries.io/github/GNS3/gns3-web-ui)
|
||||
[![Packages versions](https://repology.org/badge/latest-versions/gns3.svg)](https://repology.org/metapackage/gns3/versions)
|
||||
[![Packages](https://repology.org/badge/tiny-repos/gns3.svg)](https://repology.org/metapackage/gns3/versions)
|
||||
|
||||
Test WebUI implementation for GNS3.
|
||||
|
||||
|
102
package.json
102
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gns3-web-ui",
|
||||
"version": "2020.3.0-beta.3",
|
||||
"version": "2.2.16dev1",
|
||||
"author": {
|
||||
"name": "GNS3 Technology Inc.",
|
||||
"email": "developers@gns3.com"
|
||||
@ -40,27 +40,27 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^10.0.2",
|
||||
"@angular/cdk": "^10.0.1",
|
||||
"@angular/common": "^10.0.2",
|
||||
"@angular/compiler": "^10.0.2",
|
||||
"@angular/core": "^10.0.2",
|
||||
"@angular/forms": "^10.0.2",
|
||||
"@angular/animations": "^10.1.5",
|
||||
"@angular/cdk": "^10.2.4",
|
||||
"@angular/common": "^10.1.5",
|
||||
"@angular/compiler": "^10.1.5",
|
||||
"@angular/core": "^10.1.5",
|
||||
"@angular/forms": "^10.1.5",
|
||||
"@angular/http": "^7.2.16",
|
||||
"@angular/material": "^10.0.1",
|
||||
"@angular/platform-browser": "^10.0.2",
|
||||
"@angular/platform-browser-dynamic": "^10.0.2",
|
||||
"@angular/router": "^10.0.2",
|
||||
"@sentry/browser": "^5.18.0",
|
||||
"@types/jest": "^26.0.3",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"angular-draggable-droppable": "^4.5.1",
|
||||
"@angular/material": "^10.2.4",
|
||||
"@angular/platform-browser": "^10.1.5",
|
||||
"@angular/platform-browser-dynamic": "^10.1.5",
|
||||
"@angular/router": "^10.1.5",
|
||||
"@sentry/browser": "^5.26.0",
|
||||
"@types/jest": "^26.0.14",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"angular-draggable-droppable": "^4.5.4",
|
||||
"angular-persistence": "^1.0.1",
|
||||
"angular-resizable-element": "^3.3.2",
|
||||
"angular-resizable-element": "^3.3.3",
|
||||
"angular2-draggable": "^2.3.2",
|
||||
"angular2-hotkeys": "^2.2.0",
|
||||
"angular2-indexeddb": "^1.2.3",
|
||||
"bootstrap": "4.5.0",
|
||||
"bootstrap": "4.5.2",
|
||||
"command-exists": "^1.2.9",
|
||||
"core-js": "^3.6.5",
|
||||
"css-tree": "^1.0.0-alpha.36",
|
||||
@ -68,64 +68,64 @@
|
||||
"file-saver": "^2.0.2",
|
||||
"ini": "^1.3.5",
|
||||
"material-design-icons": "^3.0.1",
|
||||
"ng-circle-progress": "^1.5.1",
|
||||
"ng-circle-progress": "^1.6.0",
|
||||
"ng2-file-upload": "^1.3.0",
|
||||
"ngx-childprocess": "^0.0.6",
|
||||
"ngx-device-detector": "^1.4.5",
|
||||
"ngx-device-detector": "^2.0.0",
|
||||
"ngx-electron": "^2.1.1",
|
||||
"node-fetch": "^2.6.0",
|
||||
"notosans-fontface": "1.1.0",
|
||||
"rxjs": "^6.5.5",
|
||||
"rxjs-compat": "^6.5.5",
|
||||
"save-html-as-image": "^1.3.3",
|
||||
"node-fetch": "^2.6.1",
|
||||
"notosans-fontface": "1.2.2",
|
||||
"rxjs": "^6.6.3",
|
||||
"rxjs-compat": "^6.6.3",
|
||||
"save-html-as-image": "^1.3.4",
|
||||
"save-svg-as-png": "^1.4.14",
|
||||
"snyk": "^1.361.3",
|
||||
"svg-crowbar": "^0.6.0",
|
||||
"snyk": "^1.413.3",
|
||||
"svg-crowbar": "^0.6.1",
|
||||
"tree-kill": "^1.2.1",
|
||||
"tslib": "^2.0.0",
|
||||
"tslib": "^2.0.3",
|
||||
"typeface-roboto": "^0.0.75",
|
||||
"xterm": "^4.1.0",
|
||||
"xterm": "^4.9.0",
|
||||
"xterm-addon-attach": "^0.6.0",
|
||||
"xterm-addon-fit": "^0.4.0",
|
||||
"yargs": "^15.3.1",
|
||||
"zone.js": "~0.10.3"
|
||||
"yargs": "^16.0.3",
|
||||
"zone.js": "~0.11.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.1000.0",
|
||||
"@angular/cli": "^10.0.0",
|
||||
"@angular/compiler-cli": "^10.0.2",
|
||||
"@angular/language-service": "^10.0.2",
|
||||
"@sentry/cli": "^1.53.0",
|
||||
"@sentry/electron": "^1.3.0",
|
||||
"@types/jasmine": "^3.5.11",
|
||||
"@angular-devkit/build-angular": "^0.1001.6",
|
||||
"@angular/cli": "^10.1.6",
|
||||
"@angular/compiler-cli": "^10.1.5",
|
||||
"@angular/language-service": "^10.1.5",
|
||||
"@sentry/cli": "^1.58.0",
|
||||
"@sentry/electron": "^2.0.1",
|
||||
"@types/jasmine": "^3.5.14",
|
||||
"@types/jasminewd2": "^2.0.8",
|
||||
"@types/node": "12.12.6",
|
||||
"codelyzer": "^5.2.2",
|
||||
"electron": "^9.0.5",
|
||||
"electron-builder": "22.7.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"jasmine-core": "^3.5.0",
|
||||
"jasmine-spec-reporter": "^5.0.2",
|
||||
"@types/node": "14.11.2",
|
||||
"codelyzer": "^6.0.1",
|
||||
"electron": "^10.1.3",
|
||||
"electron-builder": "22.8.1",
|
||||
"file-loader": "^6.1.1",
|
||||
"jasmine-core": "^3.6.0",
|
||||
"jasmine-spec-reporter": "^6.0.0",
|
||||
"jquery": "^3.5.1",
|
||||
"karma": "^5.1.0",
|
||||
"karma": "^5.2.3",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||
"karma-jasmine": "^3.3.1",
|
||||
"karma-jasmine": "^4.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.5.4",
|
||||
"license-checker": "^25.0.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"popper.js": "^1.16.1",
|
||||
"prettier": "^2.0.5",
|
||||
"prettier": "^2.1.2",
|
||||
"protractor": "^7.0.0",
|
||||
"replace": "^1.2.0",
|
||||
"rxjs-tslint": "^0.1.8",
|
||||
"ts-mockito": "^2.6.1",
|
||||
"ts-node": "~8.10.2",
|
||||
"tslint": "^6.1.2",
|
||||
"ts-node": "~9.0.0",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "^3.9.5",
|
||||
"webpack": "^4.43.0"
|
||||
"typescript": "^4.0.3",
|
||||
"webpack": "^4.44.2"
|
||||
},
|
||||
"greenkeeper": {
|
||||
"ignore": [
|
||||
|
@ -5,3 +5,5 @@ packaging==19.0
|
||||
appdirs==1.4.3
|
||||
psutil==5.6.7
|
||||
jsonschema==2.6.0 # lock down jsonschema, 3.0 makes problems
|
||||
|
||||
urllib3>=1.25.9 # not directly required, pinned by Snyk to avoid a vulnerability
|
@ -1,5 +1,16 @@
|
||||
GNS3 WebUI is web implementation of user interface for GNS3 software.
|
||||
|
||||
Current version: 2020.4.0-beta.1
|
||||
|
||||
Bug Fixes & enhancements
|
||||
- symbol is not properly selected in change symbol dialog
|
||||
- issue when using the scroll wheel on the web console
|
||||
- missing settings for Docker nodes
|
||||
- error on servers page
|
||||
|
||||
What's new
|
||||
- double click nodes to open the console
|
||||
|
||||
Current version: 2020.3.0-beta.3
|
||||
|
||||
Bug Fixes & enhancements
|
||||
|
@ -37,6 +37,7 @@ import { ProjectMapComponent } from './components/project-map/project-map.compon
|
||||
import { ServersComponent } from './components/servers/servers.component';
|
||||
import { AddServerDialogComponent } from './components/servers/add-server-dialog/add-server-dialog.component';
|
||||
import { ContextMenuComponent } from './components/project-map/context-menu/context-menu.component';
|
||||
import { ContextConsoleMenuComponent } from './components/project-map/context-console-menu/context-console-menu.component';
|
||||
import { StartNodeActionComponent } from './components/project-map/context-menu/actions/start-node-action/start-node-action.component';
|
||||
import { StopNodeActionComponent } from './components/project-map/context-menu/actions/stop-node-action/stop-node-action.component';
|
||||
import { TemplateComponent } from './components/template/template.component';
|
||||
@ -226,6 +227,8 @@ import { ConfiguratorDialogVmwareComponent } from './components/project-map/node
|
||||
import { ConfiguratorDialogIouComponent } from './components/project-map/node-editors/configurator/iou/configurator-iou.component';
|
||||
import { ConfiguratorDialogIosComponent } from './components/project-map/node-editors/configurator/ios/configurator-ios.component';
|
||||
import { ConfiguratorDialogDockerComponent } from './components/project-map/node-editors/configurator/docker/configurator-docker.component';
|
||||
import { EditNetworkConfigurationDialogComponent } from './components/project-map/node-editors/configurator/docker/edit-network-configuration/edit-network-configuration.component';
|
||||
import { ConfigureCustomAdaptersDialogComponent } from './components/project-map/node-editors/configurator/docker/configure-custom-adapters/configure-custom-adapters.component';
|
||||
import { ConfiguratorDialogNatComponent } from './components/project-map/node-editors/configurator/nat/configurator-nat.component';
|
||||
import { ConfiguratorDialogTracengComponent } from './components/project-map/node-editors/configurator/traceng/configurator-traceng.component';
|
||||
import { AddTracengTemplateComponent } from './components/preferences/traceng/add-traceng/add-traceng-template.component';
|
||||
@ -249,7 +252,6 @@ import { AlignVerticallyActionComponent } from './components/project-map/context
|
||||
import { ConfirmationBottomSheetComponent } from './components/projects/confirmation-bottomsheet/confirmation-bottomsheet.component';
|
||||
import { TemplateFilter } from './filters/templateFilter.pipe';
|
||||
import { NotificationService } from './services/notification.service';
|
||||
import { DeviceDetectorModule } from 'ngx-device-detector';
|
||||
import { ConfigDialogComponent } from './components/project-map/context-menu/dialogs/config-dialog/config-dialog.component';
|
||||
import { Gns3vmComponent } from './components/preferences/gns3vm/gns3vm.component';
|
||||
import { Gns3vmService } from './services/gns3vm.service';
|
||||
@ -281,6 +283,8 @@ import { ApplianceInfoDialogComponent } from './components/project-map/new-templ
|
||||
import { ResetLinkActionComponent } from './components/project-map/context-menu/actions/reset-link/reset-link-action.component';
|
||||
import { InformationDialogComponent } from './components/dialogs/information-dialog.component';
|
||||
import { TemplateNameDialogComponent } from './components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component';
|
||||
import { UpdatesService } from './services/updates.service';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -297,6 +301,7 @@ import { TemplateNameDialogComponent } from './components/project-map/new-templa
|
||||
DefaultLayoutComponent,
|
||||
ProgressDialogComponent,
|
||||
ContextMenuComponent,
|
||||
ContextConsoleMenuComponent,
|
||||
StartNodeActionComponent,
|
||||
StopNodeActionComponent,
|
||||
TemplateComponent,
|
||||
@ -466,7 +471,9 @@ import { TemplateNameDialogComponent } from './components/project-map/new-templa
|
||||
ChangeHostnameDialogComponent,
|
||||
ApplianceInfoDialogComponent,
|
||||
InformationDialogComponent,
|
||||
TemplateNameDialogComponent
|
||||
TemplateNameDialogComponent,
|
||||
ConfigureCustomAdaptersDialogComponent,
|
||||
EditNetworkConfigurationDialogComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@ -487,7 +494,6 @@ import { TemplateNameDialogComponent } from './components/project-map/new-templa
|
||||
DragDropModule,
|
||||
NgxChildProcessModule,
|
||||
MATERIAL_IMPORTS,
|
||||
DeviceDetectorModule.forRoot(),
|
||||
NgCircleProgressModule.forRoot()
|
||||
],
|
||||
providers: [
|
||||
@ -562,7 +568,8 @@ import { TemplateNameDialogComponent } from './components/project-map/new-templa
|
||||
ServerResolve,
|
||||
ConsoleGuard,
|
||||
Title,
|
||||
ApplianceService
|
||||
ApplianceService,
|
||||
UpdatesService
|
||||
],
|
||||
entryComponents: [
|
||||
AddServerDialogComponent,
|
||||
@ -608,7 +615,9 @@ import { TemplateNameDialogComponent } from './components/project-map/new-templa
|
||||
AdbutlerComponent,
|
||||
NewTemplateDialogComponent,
|
||||
ChangeHostnameDialogComponent,
|
||||
ApplianceInfoDialogComponent
|
||||
ApplianceInfoDialogComponent,
|
||||
ConfigureCustomAdaptersDialogComponent,
|
||||
EditNetworkConfigurationDialogComponent
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
@ -104,6 +104,14 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.mapSettings.isReadOnly = value;
|
||||
}
|
||||
|
||||
resize(val: boolean) {
|
||||
if (val) {
|
||||
this.svg.attr('height', window.innerHeight + window.scrollY - 16);
|
||||
} else {
|
||||
this.svg.attr('height', this.height);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||
if (
|
||||
(changes['width'] && !changes['width'].isFirstChange()) ||
|
||||
@ -158,6 +166,7 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
);
|
||||
|
||||
this.gridVisibility = localStorage.getItem('gridVisibility') === 'true' ? 1 : 0;
|
||||
this.mapSettingsService.isScrollDisabled.subscribe(val => this.resize(val));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
@ -65,7 +65,7 @@ export class Node {
|
||||
console_auto_start: boolean;
|
||||
console_host: string;
|
||||
console_type: string;
|
||||
custom_adapters?: CustomAdapter[];
|
||||
custom_adapters?: any[];
|
||||
ethernet_adapters?: any;
|
||||
serial_adapters?: any;
|
||||
first_port_name: string;
|
||||
|
@ -15,6 +15,7 @@ import { MapSettingsService } from '../../services/mapsettings.service';
|
||||
@Injectable()
|
||||
export class NodeWidget implements Widget {
|
||||
public onContextMenu = new EventEmitter<NodeContextMenu>();
|
||||
public onContextConsoleMenu = new EventEmitter<NodeContextMenu>();
|
||||
public onNodeClicked = new EventEmitter<NodeClicked>();
|
||||
|
||||
constructor(
|
||||
@ -85,13 +86,19 @@ export class NodeWidget implements Widget {
|
||||
event.preventDefault();
|
||||
self.onContextMenu.emit(new NodeContextMenu(event, n));
|
||||
})
|
||||
.on('dblclick', function(n: MapNode, i: number) {
|
||||
event.preventDefault();
|
||||
self.onContextConsoleMenu.emit(new NodeContextMenu(event, n));
|
||||
})
|
||||
.attr('xnode:href', (n: MapNode) => n.symbolUrl)
|
||||
.attr('width', (n: MapNode) => {
|
||||
if (n.nodeType === 'cloud' || n.nodeType === 'nat') return 120;
|
||||
if (!n.width) return 60;
|
||||
if (n.width > 64) return 64;
|
||||
return n.width;
|
||||
})
|
||||
.attr('height', (n: MapNode) => {
|
||||
if (n.nodeType === 'cloud' || n.nodeType === 'nat') return 60;
|
||||
if (!n.height) return 60;
|
||||
if (n.height > 64) return 64;
|
||||
return n.height;
|
||||
|
@ -9,7 +9,7 @@ import * as Sentry from "@sentry/browser";
|
||||
import { BrowserOptions, init } from '@sentry/browser';
|
||||
|
||||
const config = {
|
||||
dsn: "https://d6f2d7fc84e74b05ac017753ef7bfff5@o19455.ingest.sentry.io/842726"
|
||||
dsn: "https://d8be3a98530f49eb90968ff396db326c@o19455.ingest.sentry.io/842726"
|
||||
};
|
||||
init(config as BrowserOptions);
|
||||
|
||||
@ -59,12 +59,12 @@ export class SentryErrorHandler implements ErrorHandler {
|
||||
|
||||
// Capture handled exception and send it to Sentry.
|
||||
const eventId = Sentry.captureException(extractedError);
|
||||
|
||||
|
||||
// When in development mode, log the error to console for immediate feedback.
|
||||
if (!environment.production) {
|
||||
console.error(extractedError);
|
||||
}
|
||||
|
||||
|
||||
// Optionally show user dialog to provide details on what happened.
|
||||
// Sentry.showReportDialog({ eventId });
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import { SentryErrorHandler } from './sentry-error-handler';
|
||||
@Injectable()
|
||||
export class ToasterErrorHandler extends SentryErrorHandler {
|
||||
handleError(err: any): void {
|
||||
if (err.error && err.error.status && !(err.error.status === 403 || err.error.status === 404 || err.error.status === 409)) {
|
||||
if (err.error && err.error.status && !(err.error.status === 400 || err.error.status === 403 || err.error.status === 404 || err.error.status === 409)) {
|
||||
super.handleError(err);
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,7 @@
|
||||
|
||||
.buttonSelected {
|
||||
border-width: 3px;
|
||||
border-color: #0097a7;
|
||||
width: 77px;
|
||||
background: #0097a7!important;
|
||||
}
|
||||
|
||||
.image {
|
||||
@ -33,7 +32,7 @@
|
||||
}
|
||||
|
||||
.imageSelected {
|
||||
margin-left: -3px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
|
@ -21,7 +21,7 @@
|
||||
(mousedown)="toggleDragging(true)"
|
||||
[ngStyle]="style"> -->
|
||||
|
||||
<div class="consoleHeader">
|
||||
<div class="consoleHeader" [ngClass]="{lightThemeConsoleHeader: isLightThemeEnabled}">
|
||||
<mat-tab-group class="tabs" [selectedIndex]="selected.value" (selectedIndexChange)="selected.setValue($event)">
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
@ -32,7 +32,7 @@
|
||||
<mat-tab *ngFor="let node of nodes; let index = index" [label]="tab">
|
||||
<ng-template mat-tab-label>
|
||||
<div class="col" style="margin-left: 20px;">{{node.name}}</div>
|
||||
<button style="color:white" mat-icon-button (click)="removeTab(index)">
|
||||
<button [ngClass]="{lightThemeConsoleHeader: isLightThemeEnabled}" style="color:white" mat-icon-button (click)="removeTab(index)">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</ng-template>
|
||||
@ -40,20 +40,20 @@
|
||||
|
||||
</mat-tab-group>
|
||||
|
||||
<button *ngIf="!isMinimized" style="color:white" mat-icon-button (click)="minimize(true)">
|
||||
<button *ngIf="!isMinimized" [ngClass]="{lightThemeConsoleHeader: isLightThemeEnabled}" style="color:white" mat-icon-button (click)="minimize(true)">
|
||||
<mat-icon>remove</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="isMinimized" style="color:white" mat-icon-button (click)="minimize(false)">
|
||||
<button *ngIf="isMinimized" [ngClass]="{lightThemeConsoleHeader: isLightThemeEnabled}" style="color:white" mat-icon-button (click)="minimize(false)">
|
||||
<mat-icon>web_asset</mat-icon>
|
||||
</button>
|
||||
<button style="color:white" mat-icon-button (click)="close()">
|
||||
<button [ngClass]="{lightThemeConsoleHeader: isLightThemeEnabled}" style="color:white" mat-icon-button (click)="close()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<app-log-console [hidden]="!(selected.value===0) || isMinimized" [server]="server" [project]="project"></app-log-console>
|
||||
|
||||
<div class="xterm-console" [hidden]="isMinimized" *ngFor="let node of nodes; let index = index">
|
||||
<div (mouseover)="disableScroll($event)" (mouseout)="enableScroll($event)" class="xterm-console" [hidden]="isMinimized" *ngFor="let node of nodes; let index = index">
|
||||
<app-web-console [hidden]="!(selected.value===(index+1))" [server]="server" [node]="nodes[index]"></app-web-console>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -41,6 +41,11 @@
|
||||
background: #263238!important;
|
||||
}
|
||||
|
||||
.lightThemeConsoleHeader {
|
||||
background: white!important;
|
||||
color: black!important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .mat-tab-label {
|
||||
height: 3rem !important;
|
||||
min-width: 8rem !important;
|
||||
|
@ -7,6 +7,7 @@ import { ThemeService } from '../../../services/theme.service';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
import { Node } from '../../../cartography/models/node';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -33,7 +34,8 @@ export class ConsoleWrapperComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private consoleService: NodeConsoleService,
|
||||
private themeService: ThemeService
|
||||
private themeService: ThemeService,
|
||||
private mapSettingsService: MapSettingsService
|
||||
) {}
|
||||
|
||||
nodes: Node[] = [];
|
||||
@ -148,4 +150,12 @@ export class ConsoleWrapperComponent implements OnInit {
|
||||
close() {
|
||||
this.closeConsole.emit(false);
|
||||
}
|
||||
|
||||
enableScroll(e) {
|
||||
this.mapSettingsService.isScrollDisabled.next(false);
|
||||
}
|
||||
|
||||
disableScroll(e) {
|
||||
this.mapSettingsService.isScrollDisabled.next(true);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
<div class="context-menu" [style.left]="leftPosition" [style.top]="topPosition">
|
||||
<span [matMenuTriggerFor]="contextConsoleMenu"></span>
|
||||
<mat-menu #contextConsoleMenu="matMenu" class="context-menu-items">
|
||||
<span class="title">
|
||||
Choose default behavior for double click on node
|
||||
</span>
|
||||
<button mat-menu-item (click)="openConsole()">
|
||||
<mat-icon>web_asset</mat-icon>
|
||||
<span>Open console</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openWebConsole()">
|
||||
<mat-icon>http</mat-icon>
|
||||
<span>Open web console</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="openWebConsoleInNewTab()">
|
||||
<mat-icon>http</mat-icon>
|
||||
<span>Open web console in new tab</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
<template [hidden]="true" #container></template>
|
@ -0,0 +1,12 @@
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
min-height: 0px;
|
||||
}
|
||||
|
||||
.mat-menu-panel ng-trigger ng-trigger-transformMenu ng-tns-c7-5 context-menu-items mat-menu-after mat-menu-below ng-star-inserted mat-elevation-z4 {
|
||||
min-height: 0px!important;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 10px;
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ProjectService } from '../../../services/project.service';
|
||||
import { MockedProjectService } from '../../projects/add-blank-project-dialog/add-blank-project-dialog.component.spec';
|
||||
import { MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
|
||||
import { Server } from '../../../models/server';
|
||||
import { ElectronService } from 'ngx-electron';
|
||||
import { ContextConsoleMenuComponent } from './context-console-menu.component';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
import { ConsoleService } from '../../../services/settings/console.service';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
import { MockedToasterService } from '../../../services/toaster.service.spec';
|
||||
import { Router } from '@angular/router';
|
||||
import { Node } from '../../../cartography/models/node';
|
||||
import { ConsoleDeviceActionComponent } from '../context-menu/actions/console-device-action/console-device-action.component';
|
||||
import { ConsoleDeviceActionBrowserComponent } from '../context-menu/actions/console-device-action-browser/console-device-action-browser.component';
|
||||
|
||||
fdescribe('ContextConsoleMenuComponent', () => {
|
||||
let component: ContextConsoleMenuComponent;
|
||||
let fixture: ComponentFixture<ContextConsoleMenuComponent>;
|
||||
let toasterService: MockedToasterService = new MockedToasterService();
|
||||
let router = {
|
||||
url: '',
|
||||
navigate: jasmine.createSpy('navigate')
|
||||
};
|
||||
let node = {
|
||||
status: 'started'
|
||||
};
|
||||
let mapSettingsService = new MapSettingsService();
|
||||
|
||||
beforeEach(async(() => {
|
||||
const electronMock = {
|
||||
isElectronApp: true
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatMenuModule, BrowserModule],
|
||||
providers: [
|
||||
{ provide: ChangeDetectorRef },
|
||||
{ provide: ProjectService, useClass: MockedProjectService },
|
||||
{ provide: ElectronService, useValue: electronMock },
|
||||
{ provide: MapSettingsService, useValue: mapSettingsService },
|
||||
{ provide: NodeConsoleService },
|
||||
{ provide: ConsoleService },
|
||||
{ provide: ToasterService, useValue: toasterService },
|
||||
{ provide: Router, useValue: router }
|
||||
],
|
||||
declarations: [
|
||||
ContextConsoleMenuComponent,
|
||||
ConsoleDeviceActionComponent,
|
||||
ConsoleDeviceActionBrowserComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ContextConsoleMenuComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.server = {location: 'local'} as Server;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should define property if running in electron ', () => {
|
||||
expect(component.isElectronApp).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should open menu if there is no default settings', () => {
|
||||
let spy = spyOn(component.contextConsoleMenu, 'openMenu');
|
||||
localStorage.removeItem('consoleContextMenu');
|
||||
|
||||
component.openMenu(node as unknown as Node, 0, 0);
|
||||
|
||||
expect(spy.calls.any()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call open web console when web console action in settings', () => {
|
||||
let spy = spyOn(component, 'openWebConsole');
|
||||
mapSettingsService.setConsoleContextMenuAction('web console');
|
||||
|
||||
component.openMenu(node as unknown as Node, 0, 0);
|
||||
|
||||
expect(spy.calls.any()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call open web console in new tab when web console in new tab action in settings', () => {
|
||||
let spy = spyOn(component, 'openWebConsoleInNewTab');
|
||||
mapSettingsService.setConsoleContextMenuAction('web console in new tab');
|
||||
|
||||
component.openMenu(node as unknown as Node, 0, 0);
|
||||
|
||||
expect(spy.calls.any()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call open console when console action in settings', () => {
|
||||
let spy = spyOn(component, 'openConsole');
|
||||
mapSettingsService.setConsoleContextMenuAction('console');
|
||||
|
||||
component.openMenu(node as unknown as Node, 0, 0);
|
||||
|
||||
expect(spy.calls.any()).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,112 @@
|
||||
import { ChangeDetectorRef, Component, ComponentFactory, ComponentRef, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
|
||||
import { MatMenuTrigger } from '@angular/material/menu';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { Node } from '../../../cartography/models/node';
|
||||
import { Server } from '../../../models/server';
|
||||
import { Project } from '../../../models/project';
|
||||
import { MapSettingsService } from '../../../services/mapsettings.service';
|
||||
import { ElectronService } from 'ngx-electron';
|
||||
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ComponentFactoryResolver } from '@angular/core';
|
||||
import { ConsoleDeviceActionComponent } from '../context-menu/actions/console-device-action/console-device-action.component';
|
||||
import { ConsoleDeviceActionBrowserComponent } from '../context-menu/actions/console-device-action-browser/console-device-action-browser.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-context-console-menu',
|
||||
templateUrl: './context-console-menu.component.html',
|
||||
styleUrls: ['./context-console-menu.component.scss']
|
||||
})
|
||||
export class ContextConsoleMenuComponent implements OnInit {
|
||||
@Input() project: Project;
|
||||
@Input() server: Server;
|
||||
@ViewChild(MatMenuTrigger) contextConsoleMenu: MatMenuTrigger;
|
||||
@ViewChild("container", { read: ViewContainerRef }) container;
|
||||
componentRef: ComponentRef<ConsoleDeviceActionComponent>;
|
||||
componentBrowserRef: ComponentRef<ConsoleDeviceActionBrowserComponent>;
|
||||
|
||||
topPosition;
|
||||
leftPosition;
|
||||
isElectronApp = false;
|
||||
node: Node;;
|
||||
|
||||
constructor(
|
||||
private sanitizer: DomSanitizer,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private mapSettingsService: MapSettingsService,
|
||||
private electronService: ElectronService,
|
||||
private consoleService: NodeConsoleService,
|
||||
private toasterService: ToasterService,
|
||||
private router: Router,
|
||||
private resolver: ComponentFactoryResolver
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.setPosition(0, 0);
|
||||
this.isElectronApp = this.electronService.isElectronApp;
|
||||
}
|
||||
|
||||
public setPosition(top: number, left: number) {
|
||||
this.topPosition = this.sanitizer.bypassSecurityTrustStyle(top + 'px');
|
||||
this.leftPosition = this.sanitizer.bypassSecurityTrustStyle(left + 'px');
|
||||
this.changeDetector.detectChanges();
|
||||
}
|
||||
|
||||
public openMenu(node: Node, top: number, left: number) {
|
||||
this.node = node;
|
||||
let action = this.mapSettingsService.getConsoleContextManuAction();
|
||||
if (action) {
|
||||
if (action === 'web console') {
|
||||
this.openWebConsole();
|
||||
} else if (action === 'web console in new tab') {
|
||||
this.openWebConsoleInNewTab();
|
||||
} else if (action === 'console') {
|
||||
this.openConsole();
|
||||
}
|
||||
} else {
|
||||
this.setPosition(top, left);
|
||||
this.contextConsoleMenu.openMenu();
|
||||
}
|
||||
}
|
||||
|
||||
openConsole() {
|
||||
this.mapSettingsService.setConsoleContextMenuAction('console');
|
||||
if (this.isElectronApp) {
|
||||
const factory: ComponentFactory<ConsoleDeviceActionComponent> = this.resolver.resolveComponentFactory(ConsoleDeviceActionComponent);
|
||||
this.componentRef = this.container.createComponent(factory);
|
||||
this.componentRef.instance.server = this.server;
|
||||
this.componentRef.instance.nodes = [this.node];
|
||||
|
||||
this.componentRef.instance.console();
|
||||
} else {
|
||||
const factory: ComponentFactory<ConsoleDeviceActionBrowserComponent> = this.resolver.resolveComponentFactory(ConsoleDeviceActionBrowserComponent);
|
||||
this.componentBrowserRef = this.container.createComponent(factory);
|
||||
this.componentBrowserRef.instance.server = this.server;
|
||||
this.componentBrowserRef.instance.node = this.node;
|
||||
|
||||
this.componentBrowserRef.instance.openConsole();
|
||||
}
|
||||
}
|
||||
|
||||
openWebConsole() {
|
||||
this.mapSettingsService.setConsoleContextMenuAction('web console');
|
||||
if (this.node.status === 'started') {
|
||||
this.mapSettingsService.logConsoleSubject.next(true);
|
||||
this.consoleService.openConsoleForNode(this.node);
|
||||
} else {
|
||||
this.toasterService.error('To open console please start the node');
|
||||
}
|
||||
}
|
||||
|
||||
openWebConsoleInNewTab() {
|
||||
this.mapSettingsService.setConsoleContextMenuAction('web console in new tab');
|
||||
if (this.node.status === 'started') {
|
||||
let url = this.router.url.split('/');
|
||||
let urlString = `/static/web-ui/${url[1]}/${url[2]}/${url[3]}/${url[4]}/nodes/${this.node.node_id}`
|
||||
window.open(urlString);
|
||||
} else {
|
||||
this.toasterService.error('To open console please start the node');
|
||||
}
|
||||
}
|
||||
}
|
@ -33,12 +33,12 @@ export class ConsoleDeviceActionBrowserComponent {
|
||||
this.node.console_host = this.server.host;
|
||||
}
|
||||
|
||||
if (this.node.console_type === "telnet") {
|
||||
location.assign(`gns3+telnet://${this.node.console_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 === "vnc") {
|
||||
location.assign(`gns3+vnc://${this.node.console_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 === "spice") {
|
||||
location.assign(`gns3+spice://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`);
|
||||
if (this.node.console_type === "telnet" || this.node.console_type === "vnc" || this.node.console_type === "spice") {
|
||||
try {
|
||||
location.assign(`gns3+${this.node.console_type}://${this.node.console_host}:${this.node.console}?name=${this.node.name}&project_id=${this.node.project_id}&node_id=${this.node.node_id}`);
|
||||
} catch (e) {
|
||||
this.toasterService.error(e);
|
||||
}
|
||||
} else {
|
||||
this.toasterService.error("Supported console types: telnet, vnc, spice.");
|
||||
}
|
||||
|
@ -5,4 +5,4 @@
|
||||
|
||||
.mat-menu-panel ng-trigger ng-trigger-transformMenu ng-tns-c7-5 context-menu-items mat-menu-after mat-menu-below ng-star-inserted mat-elevation-z4 {
|
||||
min-height: 0px!important;
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import { LogEventsDataSource } from './log-events-datasource';
|
||||
import { HttpServer, ServerErrorHandler } from '../../../services/http-server.service';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
|
||||
export class MockedProjectWebServiceHandler {
|
||||
public nodeNotificationEmitter = new EventEmitter<WebServiceMessage>();
|
||||
@ -41,7 +42,8 @@ describe('LogConsoleComponent', () => {
|
||||
{ provide: NodeService, useValue: mockedNodeService },
|
||||
{ provide: NodesDataSource, useValue: mockedNodesDataSource },
|
||||
{ provide: LogEventsDataSource, useClass: LogEventsDataSource },
|
||||
{ provide: HttpServer, useValue: httpServer }
|
||||
{ provide: HttpServer, useValue: httpServer },
|
||||
{ provide: NodeConsoleService }
|
||||
],
|
||||
declarations: [LogConsoleComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
@ -74,7 +76,7 @@ describe('LogConsoleComponent', () => {
|
||||
|
||||
component.handleCommand();
|
||||
|
||||
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Current version: 2020.3.0-beta.2'});
|
||||
expect(component.showMessage).toHaveBeenCalledWith({type: 'command', message: 'Current version: 2020.3.0-beta.4'});
|
||||
});
|
||||
|
||||
it('should call show message when unknown command entered', () => {
|
||||
|
@ -357,25 +357,14 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
fileReader.readAsText(file);
|
||||
}
|
||||
|
||||
checkImage(image: Image): boolean {
|
||||
if (this.applianceToInstall.qemu) {
|
||||
if (this.qemuImages.filter(n => n.filename === image.filename).length > 0) return true;
|
||||
} else if (this.applianceToInstall.dynamips) {
|
||||
if (this.iosImages.filter(n => n.filename === image.filename).length > 0) return true;
|
||||
} else if (this.applianceToInstall.iou) {
|
||||
if (this.iouImages.filter(n => n.filename === image.filename).length > 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
checkImageFromVersion(image: string): boolean {
|
||||
let imageToInstall = this.applianceToInstall.images.filter(n => n.filename === image)[0];
|
||||
if (this.applianceToInstall.qemu) {
|
||||
if (this.qemuImages.filter(n => n.filename === image).length > 0) return true;
|
||||
if (this.qemuImages.filter(n => n.md5sum === imageToInstall.md5sum).length > 0) return true;
|
||||
} else if (this.applianceToInstall.dynamips) {
|
||||
if (this.iosImages.filter(n => n.filename === image).length > 0) return true;
|
||||
if (this.iosImages.filter(n => n.md5sum === imageToInstall.md5sum).length > 0) return true;
|
||||
} else if (this.applianceToInstall.iou) {
|
||||
if (this.iouImages.filter(n => n.filename === image).length > 0) return true;
|
||||
if (this.iouImages.filter(n => n.md5sum === imageToInstall.md5sum).length > 0) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -451,36 +440,29 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
iouTemplate.path = image.filename;
|
||||
iouTemplate.template_type = 'iou';
|
||||
|
||||
if (this.templates.filter(t => t.name === this.applianceToInstall.name).length === 0) {
|
||||
iouTemplate.name = this.applianceToInstall.name;
|
||||
|
||||
this.iouService.addTemplate(this.server, iouTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else {
|
||||
const dialogRef = this.dialog.open(TemplateNameDialogComponent, {
|
||||
width: '400px',
|
||||
height: '250px',
|
||||
autoFocus: false,
|
||||
disableClose: true
|
||||
});
|
||||
dialogRef.componentInstance.server = this.server;
|
||||
dialogRef.afterClosed().subscribe((answer: string) => {
|
||||
if (answer) {
|
||||
iouTemplate.name = answer;
|
||||
|
||||
this.iouService.addTemplate(this.server, iouTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
const dialogRef = this.dialog.open(TemplateNameDialogComponent, {
|
||||
width: '400px',
|
||||
height: '250px',
|
||||
autoFocus: false,
|
||||
disableClose: true,
|
||||
data: {
|
||||
name: this.applianceToInstall.name
|
||||
}
|
||||
});
|
||||
dialogRef.componentInstance.server = this.server;
|
||||
dialogRef.afterClosed().subscribe((answer: string) => {
|
||||
if (answer) {
|
||||
iouTemplate.name = answer;
|
||||
|
||||
this.iouService.addTemplate(this.server, iouTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createIosTemplate(image: Image) {
|
||||
@ -507,36 +489,29 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
iosTemplate.image = image.filename;
|
||||
iosTemplate.template_type = 'dynamips';
|
||||
|
||||
if (this.templates.filter(t => t.name === this.applianceToInstall.name).length === 0) {
|
||||
iosTemplate.name = this.applianceToInstall.name;
|
||||
|
||||
this.iosService.addTemplate(this.server, iosTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template as any as Template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else {
|
||||
const dialogRef = this.dialog.open(TemplateNameDialogComponent, {
|
||||
width: '400px',
|
||||
height: '250px',
|
||||
autoFocus: false,
|
||||
disableClose: true
|
||||
});
|
||||
dialogRef.componentInstance.server = this.server;
|
||||
dialogRef.afterClosed().subscribe((answer: string) => {
|
||||
if (answer) {
|
||||
iosTemplate.name = answer;
|
||||
|
||||
this.iosService.addTemplate(this.server, iosTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template as any as Template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
const dialogRef = this.dialog.open(TemplateNameDialogComponent, {
|
||||
width: '400px',
|
||||
height: '250px',
|
||||
autoFocus: false,
|
||||
disableClose: true,
|
||||
data: {
|
||||
name: this.applianceToInstall.name
|
||||
}
|
||||
});
|
||||
dialogRef.componentInstance.server = this.server;
|
||||
dialogRef.afterClosed().subscribe((answer: string) => {
|
||||
if (answer) {
|
||||
iosTemplate.name = answer;
|
||||
|
||||
this.iosService.addTemplate(this.server, iosTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template as any as Template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createDockerTemplate() {
|
||||
@ -552,36 +527,29 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
dockerTemplate.image = this.applianceToInstall.docker.image;
|
||||
dockerTemplate.template_type = 'docker';
|
||||
|
||||
if (this.templates.filter(t => t.name === this.applianceToInstall.name).length === 0) {
|
||||
dockerTemplate.name = this.applianceToInstall.name;
|
||||
|
||||
this.dockerService.addTemplate(this.server, dockerTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template as any as Template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else {
|
||||
const dialogRef = this.dialog.open(TemplateNameDialogComponent, {
|
||||
width: '400px',
|
||||
height: '250px',
|
||||
autoFocus: false,
|
||||
disableClose: true
|
||||
});
|
||||
dialogRef.componentInstance.server = this.server;
|
||||
dialogRef.afterClosed().subscribe((answer: string) => {
|
||||
if (answer) {
|
||||
dockerTemplate.name = answer;
|
||||
|
||||
this.dockerService.addTemplate(this.server, dockerTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template as any as Template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
const dialogRef = this.dialog.open(TemplateNameDialogComponent, {
|
||||
width: '400px',
|
||||
height: '250px',
|
||||
autoFocus: false,
|
||||
disableClose: true,
|
||||
data: {
|
||||
name: this.applianceToInstall.name
|
||||
}
|
||||
});
|
||||
dialogRef.componentInstance.server = this.server;
|
||||
dialogRef.afterClosed().subscribe((answer: string) => {
|
||||
if (answer) {
|
||||
dockerTemplate.name = answer;
|
||||
|
||||
this.dockerService.addTemplate(this.server, dockerTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template as any as Template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createQemuTemplateFromVersion(version: Version) {
|
||||
@ -618,36 +586,30 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
qemuTemplate.template_type = 'qemu';
|
||||
qemuTemplate.usage = this.applianceToInstall.usage;
|
||||
|
||||
if (this.templates.filter(t => t.name === this.applianceToInstall.name).length === 0) {
|
||||
qemuTemplate.name = this.applianceToInstall.name;
|
||||
|
||||
this.qemuService.addTemplate(this.server, qemuTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template as any as Template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else {
|
||||
const dialogRef = this.dialog.open(TemplateNameDialogComponent, {
|
||||
width: '400px',
|
||||
height: '250px',
|
||||
autoFocus: false,
|
||||
disableClose: true
|
||||
});
|
||||
dialogRef.componentInstance.server = this.server;
|
||||
dialogRef.afterClosed().subscribe((answer: string) => {
|
||||
if (answer) {
|
||||
qemuTemplate.name = answer;
|
||||
|
||||
this.qemuService.addTemplate(this.server, qemuTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template as any as Template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const dialogRef = this.dialog.open(TemplateNameDialogComponent, {
|
||||
width: '400px',
|
||||
height: '250px',
|
||||
autoFocus: false,
|
||||
disableClose: true,
|
||||
data: {
|
||||
name: this.applianceToInstall.name
|
||||
}
|
||||
});
|
||||
dialogRef.componentInstance.server = this.server;
|
||||
dialogRef.afterClosed().subscribe((answer: string) => {
|
||||
if (answer) {
|
||||
qemuTemplate.name = answer;
|
||||
|
||||
this.qemuService.addTemplate(this.server, qemuTemplate).subscribe((template) => {
|
||||
this.templateService.newTemplateCreated.next(template as any as Template);
|
||||
this.toasterService.success('Template added');
|
||||
this.dialogRef.close();
|
||||
});
|
||||
} else{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit, EventEmitter } from '@angular/core';
|
||||
import { Component, OnInit, EventEmitter, Inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
|
||||
import { Server } from '../../../../models/server';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@ -28,13 +28,19 @@ export class TemplateNameDialogComponent implements OnInit {
|
||||
private toasterService: ToasterService,
|
||||
private formBuilder: FormBuilder,
|
||||
private templateNameValidator: ProjectNameValidator,
|
||||
private templateService: TemplateService
|
||||
private templateService: TemplateService,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
let name = this.data['name'];
|
||||
this.templateNameForm = this.formBuilder.group({
|
||||
templateName: new FormControl(null, [Validators.required, this.templateNameValidator.get], [templateNameAsyncValidator(this.server, this.templateService)])
|
||||
templateName: new FormControl(name, [Validators.required, this.templateNameValidator.get], [templateNameAsyncValidator(this.server, this.templateService)])
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.templateNameForm.controls['templateName'].markAsTouched();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
get form() {
|
||||
|
@ -27,7 +27,10 @@
|
||||
<mat-form-field class="form-field">
|
||||
<input formControlName="cpus" matInput type="number" [(ngModel)]="node.properties.cpus" placeholder="Maximum CPUs">
|
||||
</mat-form-field>
|
||||
<!-- custom adapters -->
|
||||
|
||||
<button mat-button class="form-field" (click)="configureCustomAdapters()">
|
||||
Configure custom adapters
|
||||
</button>
|
||||
|
||||
<mat-form-field class="select">
|
||||
<mat-select [ngModelOptions]="{standalone: true}" placeholder="Console type" [(ngModel)]="node.console_type">
|
||||
@ -56,6 +59,9 @@
|
||||
<input matInput formControlName="consoleHttpPath" type="text" [(ngModel)]="node.properties.console_http_path" placeholder="HTTP path">
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-button class="form-field" (click)="editNetworkConfiguration()">
|
||||
Edit network configuration
|
||||
</button>
|
||||
</form>
|
||||
<h6>Environment</h6>
|
||||
<mat-form-field class="form-field">
|
||||
|
@ -4,9 +4,11 @@ import { Node } from '../../../../../cartography/models/node';
|
||||
import { Server } from '../../../../../models/server';
|
||||
import { NodeService } from '../../../../../services/node.service';
|
||||
import { ToasterService } from '../../../../../services/toaster.service';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DockerConfigurationService } from '../../../../../services/docker-configuration.service';
|
||||
import { NonNegativeValidator } from '../../../../../validators/non-negative-validator';
|
||||
import { EditNetworkConfigurationDialogComponent } from './edit-network-configuration/edit-network-configuration.component';
|
||||
import { ConfigureCustomAdaptersDialogComponent } from './configure-custom-adapters/configure-custom-adapters.component';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -29,14 +31,21 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
|
||||
'1366x768',
|
||||
'1920x1080'
|
||||
];
|
||||
private conf = {
|
||||
autoFocus: false,
|
||||
width: '800px',
|
||||
disableClose: true
|
||||
};
|
||||
dialogRef;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ConfiguratorDialogDockerComponent>,
|
||||
public dialogReference: MatDialogRef<ConfiguratorDialogDockerComponent>,
|
||||
public nodeService: NodeService,
|
||||
private toasterService: ToasterService,
|
||||
private formBuilder: FormBuilder,
|
||||
private dockerConfigurationService: DockerConfigurationService,
|
||||
private nonNegativeValidator: NonNegativeValidator,
|
||||
private dialog: MatDialog
|
||||
) {
|
||||
this.generalSettingsForm = this.formBuilder.group({
|
||||
name: new FormControl('', Validators.required),
|
||||
@ -62,6 +71,20 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
|
||||
this.consoleTypes = this.dockerConfigurationService.getConsoleTypes();
|
||||
}
|
||||
|
||||
configureCustomAdapters() {
|
||||
this.dialogRef = this.dialog.open(ConfigureCustomAdaptersDialogComponent, this.conf);
|
||||
let instance = this.dialogRef.componentInstance;
|
||||
instance.server = this.server;
|
||||
instance.node = this.node;
|
||||
}
|
||||
|
||||
editNetworkConfiguration() {
|
||||
this.dialogRef = this.dialog.open(EditNetworkConfigurationDialogComponent, this.conf);
|
||||
let instance = this.dialogRef.componentInstance;
|
||||
instance.server = this.server;
|
||||
instance.node = this.node;
|
||||
}
|
||||
|
||||
onSaveClick() {
|
||||
if (this.generalSettingsForm.valid) {
|
||||
this.nodeService.updateNode(this.server, this.node).subscribe(() => {
|
||||
@ -74,6 +97,6 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
|
||||
}
|
||||
|
||||
onCancelClick() {
|
||||
this.dialogRef.close();
|
||||
this.dialogReference.close();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
<h1 mat-dialog-title>Configure custom adapters for node {{node.name}}</h1>
|
||||
|
||||
<div *ngIf="node" class="modal-form-container">
|
||||
<div class="header">
|
||||
<span class="column">
|
||||
Adapter number
|
||||
</span>
|
||||
<span class="column">
|
||||
Port name
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-list>
|
||||
<mat-list-item *ngFor="let adapter of adapters; index as i">
|
||||
<div class="header">
|
||||
<span class="column">
|
||||
Adapter {{adapter.adapter_number}}
|
||||
</span>
|
||||
<span class="column">
|
||||
<input matInput type="text" [(ngModel)]="adapter.port_name" placeholder="Edit port name">
|
||||
</span>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onCancelClick()" color="accent">Cancel</button>
|
||||
<button mat-button (click)="onSaveClick()" tabindex="2" mat-raised-button color="primary">Apply</button>
|
||||
</div>
|
@ -0,0 +1,21 @@
|
||||
th {
|
||||
border: 0px!important;
|
||||
}
|
||||
|
||||
th.mat-header-cell {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
td.mat-cell {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.column {
|
||||
width: 50%;
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import { Component, OnInit, Input, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
|
||||
import { Node } from '../../../../../../cartography/models/node';
|
||||
import { Server } from '../../../../../../models/server';
|
||||
import { NodeService } from '../../../../../../services/node.service';
|
||||
import { ToasterService } from '../../../../../../services/toaster.service';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { DockerConfigurationService } from '../../../../../../services/docker-configuration.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-configure-custom-adapters',
|
||||
templateUrl: './configure-custom-adapters.component.html',
|
||||
styleUrls: ['./configure-custom-adapters.component.scss']
|
||||
})
|
||||
export class ConfigureCustomAdaptersDialogComponent implements OnInit {
|
||||
server: Server;
|
||||
node: Node;
|
||||
displayedColumns: string[] = ['adapter_number', 'port_name'];
|
||||
adapters: CustomAdapter[] = [];
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ConfigureCustomAdaptersDialogComponent>,
|
||||
public nodeService: NodeService,
|
||||
private toasterService: ToasterService,
|
||||
private formBuilder: FormBuilder,
|
||||
private dockerConfigurationService: DockerConfigurationService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
let i: number = 0;
|
||||
if (!this.node.custom_adapters) {
|
||||
this.node.ports.forEach((port) => {
|
||||
this.adapters.push({
|
||||
adapter_number: i,
|
||||
port_name: ''
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.adapters = this.node.custom_adapters;
|
||||
}
|
||||
}
|
||||
|
||||
onSaveClick() {
|
||||
this.node.custom_adapters = this.adapters;
|
||||
this.nodeService.updateNodeWithCustomAdapters(this.server, this.node).subscribe(() => {
|
||||
this.onCancelClick();
|
||||
this.toasterService.success(`Configuration saved for node ${this.node.name}`);
|
||||
});
|
||||
}
|
||||
|
||||
onCancelClick() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomAdapter {
|
||||
adapter_number: number;
|
||||
port_name: string;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<h1 mat-dialog-title>Edit network configuration for node {{node.name}}</h1>
|
||||
|
||||
<div *ngIf="node" class="modal-form-container">
|
||||
<textarea matInput [(ngModel)]="configuration" class="textArea" type="text"></textarea>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onCancelClick()" color="accent">Cancel</button>
|
||||
<button mat-button (click)="onSaveClick()" tabindex="2" mat-raised-button color="primary">Apply</button>
|
||||
</div>
|
@ -0,0 +1,4 @@
|
||||
.textArea {
|
||||
width: 100%;
|
||||
height: 350px;
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import { Component, OnInit, Input, ViewChild } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
|
||||
import { Node } from '../../../../../../cartography/models/node';
|
||||
import { Server } from '../../../../../../models/server';
|
||||
import { NodeService } from '../../../../../../services/node.service';
|
||||
import { ToasterService } from '../../../../../../services/toaster.service';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { DockerConfigurationService } from '../../../../../../services/docker-configuration.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-network-configuration',
|
||||
templateUrl: './edit-network-configuration.component.html',
|
||||
styleUrls: ['./edit-network-configuration.component.scss']
|
||||
})
|
||||
export class EditNetworkConfigurationDialogComponent implements OnInit {
|
||||
server: Server;
|
||||
node: Node;
|
||||
configuration: string;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<EditNetworkConfigurationDialogComponent>,
|
||||
public nodeService: NodeService,
|
||||
private toasterService: ToasterService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.nodeService.getNetworkConfiguration(this.server, this.node).subscribe((response: string) => {
|
||||
this.configuration = response;
|
||||
});
|
||||
}
|
||||
|
||||
onSaveClick() {
|
||||
this.nodeService.saveNetworkConfiguration(this.server, this.node, this.configuration).subscribe((response: string) => {
|
||||
this.onCancelClick();
|
||||
this.toasterService.success(`Configuration for node ${this.node.name} saved.`);
|
||||
});
|
||||
}
|
||||
|
||||
onCancelClick() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
}
|
@ -59,6 +59,10 @@
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>Go to system status</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/settings">
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span>Go to settings</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addNewTemplate()">
|
||||
<mat-icon>control_point</mat-icon>
|
||||
<span>New template</span>
|
||||
@ -179,6 +183,7 @@
|
||||
</div>
|
||||
|
||||
<app-context-menu [project]="project" [server]="server"></app-context-menu>
|
||||
<app-context-console-menu [project]="project" [server]="server"></app-context-console-menu>
|
||||
</div>
|
||||
|
||||
<div [ngClass]="{lightTheme: isLightThemeEnabled}" class="zoom-buttons">
|
||||
|
@ -13,6 +13,7 @@ import { ProjectService } from '../../services/project.service';
|
||||
import { Server } from '../../models/server';
|
||||
import { Drawing } from '../../cartography/models/drawing';
|
||||
import { ContextMenuComponent } from './context-menu/context-menu.component';
|
||||
import { ContextConsoleMenuComponent } from './context-console-menu/context-console-menu.component';
|
||||
import { Template } from '../../models/template';
|
||||
import { NodeService } from '../../services/node.service';
|
||||
import { Symbol } from '../../models/symbol';
|
||||
@ -112,6 +113,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
public isLightThemeEnabled: boolean = false;
|
||||
|
||||
@ViewChild(ContextMenuComponent) contextMenu: ContextMenuComponent;
|
||||
@ViewChild(ContextConsoleMenuComponent) consoleContextMenu: ContextConsoleMenuComponent;
|
||||
@ViewChild(D3MapComponent) mapChild: D3MapComponent;
|
||||
@ViewChild(ProjectMapMenuComponent) projectMapMenuComponent: ProjectMapMenuComponent;
|
||||
|
||||
@ -437,6 +439,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.contextMenu.openMenuForListOfElements(drawings, nodes, labels, links, event.pageY, event.pageX);
|
||||
});
|
||||
|
||||
const onContextConsoleMenu = this.nodeWidget.onContextConsoleMenu.subscribe((eventNode: NodeContextMenu) => {
|
||||
const node = this.mapNodeToNode.convert(eventNode.node);
|
||||
this.consoleContextMenu.openMenu(node, eventNode.event.pageY, eventNode.event.pageX);
|
||||
});
|
||||
|
||||
this.projectMapSubscription.add(onLinkContextMenu);
|
||||
this.projectMapSubscription.add(onEthernetLinkContextMenu);
|
||||
this.projectMapSubscription.add(onSerialLinkContextMenu);
|
||||
@ -445,6 +452,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.projectMapSubscription.add(onContextMenu);
|
||||
this.projectMapSubscription.add(onLabelContextMenu);
|
||||
this.projectMapSubscription.add(onInterfaceLabelContextMenu);
|
||||
this.projectMapSubscription.add(onContextConsoleMenu);
|
||||
this.mapChangeDetectorRef.detectChanges();
|
||||
}
|
||||
|
||||
|
@ -1 +1 @@
|
||||
<div #terminal id="terminal"></div>
|
||||
<div class="lightTheme" #terminal id="terminal"></div>
|
||||
|
@ -0,0 +1,4 @@
|
||||
.lightTheme {
|
||||
background: white!important;
|
||||
color: black!important;
|
||||
}
|
@ -6,13 +6,14 @@ import { AttachAddon } from 'xterm-addon-attach';
|
||||
import { Node } from '../../../cartography/models/node';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { NodeConsoleService } from '../../../services/nodeConsole.service';
|
||||
import { ThemeService } from '../../../services/theme.service';
|
||||
|
||||
|
||||
@Component({
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
selector: 'app-web-console',
|
||||
templateUrl: './web-console.component.html',
|
||||
styleUrls: ['../../../../../node_modules/xterm/css/xterm.css']
|
||||
styleUrls: ['../../../../../node_modules/xterm/css/xterm.css', './web-console.component.scss']
|
||||
})
|
||||
export class WebConsoleComponent implements OnInit, AfterViewInit {
|
||||
@Input() server: Server;
|
||||
@ -21,15 +22,19 @@ export class WebConsoleComponent implements OnInit, AfterViewInit {
|
||||
|
||||
public term: Terminal = new Terminal();
|
||||
public fitAddon: FitAddon = new FitAddon();
|
||||
public isLightThemeEnabled: boolean = false;
|
||||
private copiedText: string = '';
|
||||
|
||||
@ViewChild('terminal') terminal: ElementRef;
|
||||
|
||||
constructor(
|
||||
private consoleService: NodeConsoleService
|
||||
private consoleService: NodeConsoleService,
|
||||
private themeService: ThemeService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.themeService.getActualTheme() === 'light' ? this.isLightThemeEnabled = true : this.isLightThemeEnabled = false;
|
||||
|
||||
this.consoleService.consoleResized.subscribe(ev => {
|
||||
let numberOfColumns = Math.floor(ev.width / 9);
|
||||
let numberOfRows = Math.floor(ev.height / 17);
|
||||
@ -47,6 +52,8 @@ export class WebConsoleComponent implements OnInit, AfterViewInit {
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.term.open(this.terminal.nativeElement);
|
||||
if (this.isLightThemeEnabled) this.term.setOption('theme', { background: 'white', foreground: 'black', cursor: 'black' });
|
||||
|
||||
const socket = new WebSocket(this.getUrl());
|
||||
|
||||
socket.onerror = ((event) => {
|
||||
|
@ -48,4 +48,3 @@
|
||||
<button mat-button (click)="onAddClick()" tabindex="2" mat-raised-button color="primary">Add</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -9,6 +9,7 @@ import { Version } from '../../../models/version';
|
||||
import { forkJoin } from 'rxjs';
|
||||
import { ServerService } from '../../../services/server.service';
|
||||
import { ServerDatabase } from '../../../services/server.database';
|
||||
import { from } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-server-discovery',
|
||||
@ -43,8 +44,8 @@ export class ServerDiscoveryComponent implements OnInit {
|
||||
|
||||
discoverFirstAvailableServer() {
|
||||
forkJoin(
|
||||
Observable.fromPromise(this.serverService.findAll()).pipe(map((s: Server[]) => s)),
|
||||
this.discovery()
|
||||
[from(this.serverService.findAll()).pipe(map((s: Server[]) => s)),
|
||||
this.discovery()]
|
||||
).subscribe(([local, discovered]) => {
|
||||
local.forEach(added => {
|
||||
discovered = discovered.filter(server => {
|
||||
|
@ -54,6 +54,18 @@
|
||||
</div>
|
||||
|
||||
</mat-expansion-panel>
|
||||
|
||||
<mat-expansion-panel [expanded]="false">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Updates </mat-panel-title>
|
||||
<mat-panel-description> Check for updates </mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<div class="theme-panel">
|
||||
<button mat-raised-button (click)="checkForUpdates()" class="fullWidth">Check for updates</button>
|
||||
</div>
|
||||
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</div>
|
||||
|
||||
|
@ -3,3 +3,7 @@
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.fullWidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { ToasterService } from '../../services/toaster.service';
|
||||
import { ConsoleService } from '../../services/settings/console.service';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
import { MapSettingsService } from '../../services/mapsettings.service';
|
||||
import { UpdatesService } from '../../services/updates.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
@ -20,7 +21,8 @@ export class SettingsComponent implements OnInit {
|
||||
private toaster: ToasterService,
|
||||
private consoleService: ConsoleService,
|
||||
private themeService: ThemeService,
|
||||
public mapSettingsService: MapSettingsService
|
||||
public mapSettingsService: MapSettingsService,
|
||||
public updatesService: UpdatesService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -38,4 +40,8 @@ export class SettingsComponent implements OnInit {
|
||||
setDarkMode(value: boolean) {
|
||||
this.themeService.setDarkMode(value);
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
window.open("https://gns3.com/software");
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,13 @@ export class HttpServer {
|
||||
|
||||
private getOptionsForServer<T extends HeadersOptions>(server: Server, url: string, options: T) {
|
||||
if (server.host && server.port) {
|
||||
url = `http://${server.host}:${server.port}/v2${url}`;
|
||||
if (server.authorization === 'basic') {
|
||||
url = `https://${server.host}:${server.port}/v2${url}`;
|
||||
console.log(url);
|
||||
} else {
|
||||
url = `http://${server.host}:${server.port}/v2${url}`;
|
||||
console.log(url);
|
||||
}
|
||||
} else {
|
||||
url = `/v2${url}`;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { Subject } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class MapSettingsService {
|
||||
public isScrollDisabled = new Subject<boolean>();
|
||||
public isMapLocked = new Subject<boolean>();
|
||||
public isTopologySummaryVisible: boolean = true;
|
||||
public isLogConsoleVisible: boolean = false;
|
||||
@ -22,6 +23,14 @@ export class MapSettingsService {
|
||||
this.isMapLocked.next(value);
|
||||
}
|
||||
|
||||
setConsoleContextMenuAction(action: string) {
|
||||
localStorage.setItem('consoleContextMenu', action);
|
||||
}
|
||||
|
||||
getConsoleContextManuAction(): string {
|
||||
return localStorage.getItem('consoleContextMenu');
|
||||
}
|
||||
|
||||
toggleTopologySummary(value: boolean) {
|
||||
this.isTopologySummaryVisible = value;
|
||||
}
|
||||
|
@ -149,6 +149,14 @@ export class NodeService {
|
||||
return `putty.exe -telnet \%h \%p -wt \"\%d\" -gns3 5 -skin 4`;
|
||||
}
|
||||
|
||||
getNetworkConfiguration(server: Server, node: Node) {
|
||||
return this.httpServer.get(server, `/projects/${node.project_id}/nodes/${node.node_id}/files/etc/network/interfaces`, { responseType: 'text' as 'json'});
|
||||
}
|
||||
|
||||
saveNetworkConfiguration(server: Server, node: Node, configuration: string) {
|
||||
return this.httpServer.post(server, `/projects/${node.project_id}/nodes/${node.node_id}/files/etc/network/interfaces`, configuration);
|
||||
}
|
||||
|
||||
getStartupConfiguration(server: Server, node: Node) {
|
||||
let urlPath: string = `/projects/${node.project_id}/nodes/${node.node_id}`;
|
||||
|
||||
|
11
src/app/services/updates.service.ts
Normal file
11
src/app/services/updates.service.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
@Injectable()
|
||||
export class UpdatesService {
|
||||
constructor(private httpClient: HttpClient) {}
|
||||
|
||||
getLatestVersion() {
|
||||
return this.httpClient.get('http://update.gns3.net/');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user