Merge branch 'master' into Create-templates-from-appliances

This commit is contained in:
piotrpekala7 2020-06-15 12:21:30 +02:00
commit b3109a8cc5
43 changed files with 488 additions and 186 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "gns3-web-ui", "name": "gns3-web-ui",
"version": "2020.2.0-beta.3", "version": "2020.2.0-beta.4",
"author": { "author": {
"name": "GNS3 Technology Inc.", "name": "GNS3 Technology Inc.",
"email": "developers@gns3.com" "email": "developers@gns3.com"

View File

@ -1,6 +1,19 @@
GNS3 WebUI is web implementation of user interface for GNS3 software. GNS3 WebUI is web implementation of user interface for GNS3 software.
Current version: GNS3 Web UI 2020.2.0-beta.2 Current version: GNS3 Web UI 2020.2.0-beta.4
Bug Fixes
- New port setting for GNS3 VM preferences
- Option to auto-hide menu toolbar on the left side
- Server type in template preferences
- Error when selecting existing Docker image
- Default values in templates
- TypeError: Cannot read property 'message' of undefined
- TypeError: e.error is undefined
- TypeError: Cannot read property 'placements' of null
- Creating IOS templates -> fix for platforms and network adapters
GNS3 Web UI 2020.2.0-beta.2
What's New What's New
- Drag & drop to add new nodes on topology - Drag & drop to add new nodes on topology

View File

@ -169,7 +169,7 @@ const routes: Routes = [
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes, { anchorScrolling: 'enabled', enableTracing: true, scrollPositionRestoration: 'enabled'})], imports: [RouterModule.forRoot(routes, { anchorScrolling: 'enabled', enableTracing: false, scrollPositionRestoration: 'enabled'})],
exports: [RouterModule] exports: [RouterModule]
}) })
export class AppRoutingModule {} export class AppRoutingModule {}

View File

@ -278,6 +278,8 @@ import { ConsoleGuard } from './guards/console-guard';
import { NewTemplateDialogComponent } from './components/project-map/new-template-dialog/new-template-dialog.component'; import { NewTemplateDialogComponent } from './components/project-map/new-template-dialog/new-template-dialog.component';
import { ApplianceService } from './services/appliances.service'; import { ApplianceService } from './services/appliances.service';
import { DataSourceFilter } from './filters/dataSourceFilter'; import { DataSourceFilter } from './filters/dataSourceFilter';
import { ChangeHostnameActionComponent } from './components/project-map/context-menu/actions/change-hostname/change-hostname-action.component';
import { ChangeHostnameDialogComponent } from './components/project-map/change-hostname-dialog/change-hostname-dialog.component';
if (environment.production) { if (environment.production) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', { Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -466,7 +468,9 @@ if (environment.production) {
ConsoleWrapperComponent, ConsoleWrapperComponent,
HttpConsoleNewTabActionComponent, HttpConsoleNewTabActionComponent,
WebConsoleFullWindowComponent, WebConsoleFullWindowComponent,
NewTemplateDialogComponent NewTemplateDialogComponent,
ChangeHostnameActionComponent,
ChangeHostnameDialogComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -607,7 +611,8 @@ if (environment.production) {
ConfirmationBottomSheetComponent, ConfirmationBottomSheetComponent,
ConfigDialogComponent, ConfigDialogComponent,
AdbutlerComponent, AdbutlerComponent,
NewTemplateDialogComponent NewTemplateDialogComponent,
ChangeHostnameDialogComponent
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -18,21 +18,21 @@ export class StylesToFontConverter implements Converter<string, Font> {
}); });
ast.children.forEach(child => { ast.children.forEach(child => {
if (child.property === 'font-size') { if (child.property === 'font-size' && child.value && child.value.children) {
child.value.children.forEach(value => { child.value.children.forEach(value => {
if (value.type === 'Dimension') { if (value.type === 'Dimension') {
font.font_size = parseInt(value.value); font.font_size = parseInt(value.value);
} }
}); });
} }
if (child.property === 'font-family') { if (child.property === 'font-family' && child.value && child.value.children) {
child.value.children.forEach(value => { child.value.children.forEach(value => {
if (value.type === 'Identifier') { if (value.type === 'Identifier') {
font.font_family = value.name; font.font_family = value.name;
} }
}); });
} }
if (child.property === 'font-weight') { if (child.property === 'font-weight' && child.value && child.value.children) {
child.value.children.forEach(value => { child.value.children.forEach(value => {
if (value.type === 'Identifier') { if (value.type === 'Identifier') {
font.font_weight = value.name; font.font_weight = value.name;

View File

@ -11,7 +11,7 @@ export class CssFixer {
// fixes font-size when unit (pt|px) is not defined // fixes font-size when unit (pt|px) is not defined
ast.children.forEach(child => { ast.children.forEach(child => {
if (child.property === 'font-size') { if (child.property === 'font-size' && child.value && child.value.children) {
child.value.children.forEach(value => { child.value.children.forEach(value => {
if (value.type === 'Number') { if (value.type === 'Number') {
const fontSize = value.value.toString(); const fontSize = value.value.toString();

View File

@ -30,7 +30,7 @@ export class FontFixer {
let isByIdentifier = true; let isByIdentifier = true;
ast.children.forEach(child => { ast.children.forEach(child => {
if (child.property === 'font-family') { if (child.property === 'font-family' && child.value && child.value.children) {
child.value.children.forEach(value => { child.value.children.forEach(value => {
if (value.type === 'Identifier') { if (value.type === 'Identifier') {
fontFamilyPointer = value; fontFamilyPointer = value;
@ -41,7 +41,7 @@ export class FontFixer {
} }
}); });
} }
if (child.property === 'font-size') { if (child.property === 'font-size' && child.value && child.value.children) {
child.value.children.forEach(value => { child.value.children.forEach(value => {
if (value.type === 'Dimension') { if (value.type === 'Dimension') {
fontSizePointer = value; fontSizePointer = value;

View File

@ -54,6 +54,7 @@ export class Properties {
qemu_path: string; qemu_path: string;
environment: string; environment: string;
extra_hosts: string; extra_hosts: string;
replicate_network_connection_state: boolean;
} }
export class Node { export class Node {

View File

@ -87,12 +87,14 @@ export class NodeWidget implements Widget {
}) })
.attr('xnode:href', (n: MapNode) => n.symbolUrl) .attr('xnode:href', (n: MapNode) => n.symbolUrl)
.attr('width', (n: MapNode) => { .attr('width', (n: MapNode) => {
if (!n.width) return 60 if (!n.width) return 60;
return n.width if (n.width > 64) return 64;
return n.width;
}) })
.attr('height', (n: MapNode) => { .attr('height', (n: MapNode) => {
if (!n.height) return 60 if (!n.height) return 60;
return n.height if (n.height > 64) return 64;
return n.height;
}) })
.attr('x', (n: MapNode) => 0) .attr('x', (n: MapNode) => 0)
.attr('y', (n: MapNode) => 0) .attr('y', (n: MapNode) => 0)

View File

@ -4,7 +4,8 @@ import { ToasterService } from '../../services/toaster.service';
export class ToasterErrorHandler extends RavenErrorHandler { export class ToasterErrorHandler extends RavenErrorHandler {
handleError(err: any): void { handleError(err: any): void {
super.handleError(err); super.handleError(err);
if (!err) return;
const toasterService = this.injector.get(ToasterService); const toasterService = this.injector.get(ToasterService);
if (err.error && err.error.message) { if (err.error && err.error.message) {
toasterService.error(err.error.message); toasterService.error(err.error.message);

View File

@ -24,22 +24,26 @@ export class DirectLinkComponent implements OnInit {
const serverIp = this.route.snapshot.paramMap.get('server_ip'); const serverIp = this.route.snapshot.paramMap.get('server_ip');
const serverPort = +this.route.snapshot.paramMap.get('server_port'); const serverPort = +this.route.snapshot.paramMap.get('server_port');
const projectId = this.route.snapshot.paramMap.get('project_id'); const projectId = this.route.snapshot.paramMap.get('project_id');
this.serverService.serviceInitialized.subscribe(async (value: boolean) => {
if (value) {
const servers = await this.serverService.findAll();
const server = servers.filter(server => server.host === serverIp && server.port === serverPort)[0];
const servers = await this.serverService.findAll(); if (server) {
const server = servers.filter(server => server.host === serverIp && server.port === serverPort)[0]; this.router.navigate(['/server', server.id, 'project', projectId]);
} else {
if (server) { let serverToAdd: Server = new Server();
this.router.navigate(['/server', server.id, 'project', projectId]); serverToAdd.host = serverIp;
} else { serverToAdd.port = serverPort;
let serverToAdd: Server = new Server(); serverToAdd.location = 'bundled';
serverToAdd.host = serverIp; serverToAdd.name = serverIp;
serverToAdd.port = serverPort;
serverToAdd.location = 'bundled'; this.serverService.create(serverToAdd).then((addedServer: Server) => {
serverToAdd.name = serverIp; this.router.navigate(['/server', addedServer.id, 'project', projectId]);
});
this.serverService.create(serverToAdd).then((addedServer: Server) => { }
this.router.navigate(['/server', addedServer.id, 'project', projectId]); }
}); });
}
} }
} }

View File

@ -7,7 +7,7 @@
<div class="default-content"> <div class="default-content">
<mat-card class="matCard"> <mat-card class="matCard">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run the cloud node on your local computer</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run the cloud node locally</mat-radio-button>
<mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run the cloud node on the GNS3 VM</mat-radio-button> <mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run the cloud node on the GNS3 VM</mat-radio-button>
</mat-radio-group> </mat-radio-group>

View File

@ -7,7 +7,7 @@
<div class="default-content"> <div class="default-content">
<mat-card class="matCard"> <mat-card class="matCard">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run the Ethernet Hub on your local computer</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run the Ethernet Hub locally</mat-radio-button>
<mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run the Ethernet Hub on the GNS3 VM</mat-radio-button> <mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run the Ethernet Hub on the GNS3 VM</mat-radio-button>
</mat-radio-group> </mat-radio-group>

View File

@ -7,10 +7,10 @@
<div class="default-content"> <div class="default-content">
<mat-card class="matCard"> <mat-card class="matCard">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run the Ethernet switch on your local computer</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run the Ethernet switch locally</mat-radio-button>
<mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run the Ethernet switch on the GNS3 VM</mat-radio-button> <mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run the Ethernet switch on the GNS3 VM</mat-radio-button>
</mat-radio-group> </mat-radio-group>
<form [formGroup]="formGroup"> <form [formGroup]="formGroup">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input formControlName="templateName" matInput type="text" placeholder="Template name"> <input formControlName="templateName" matInput type="text" placeholder="Template name">

View File

@ -1,34 +1,32 @@
<mat-card> <div class="menu">
<div class="menu"> <mat-radio-group aria-label="Select an option" class="radio-selection">
<mat-radio-group aria-label="Select an option" class="radio-selection"> <mat-radio-button value="1" (click)="setFilter('all')" checked>All symbols</mat-radio-button>
<mat-radio-button value="1" (click)="setFilter('all')" checked>All symbols</mat-radio-button> <mat-radio-button value="2" (click)="setFilter('builtin')">Built-in symbols</mat-radio-button>
<mat-radio-button value="2" (click)="setFilter('builtin')">Built-in symbols</mat-radio-button> <mat-radio-button value="3" (click)="setFilter('custom')">Custom symbols</mat-radio-button>
<mat-radio-button value="3" (click)="setFilter('custom')">Custom symbols</mat-radio-button> </mat-radio-group>
</mat-radio-group> <input
<input type="file"
type="file" accept=".svg, .bmp, .jpeg, .jpg, .gif, .png"
accept=".svg, .bmp, .jpeg, .jpg, .gif, .png" class="non-visible"
class="non-visible" #file
#file (change)="uploadSymbolFile($event)"/>
(change)="uploadSymbolFile($event)"/> <button mat-button (click)="file.click()">
<button mat-button (click)="file.click()"> <mat-icon>add</mat-icon>
<mat-icon>add</mat-icon> Add symbol
Add symbol </button>
</div>
<form>
<mat-form-field class="example-full-width">
<input matInput
placeholder="Search by filename"
[(ngModel)]="searchText"
[ngModelOptions]="{standalone: true}">
</mat-form-field>
</form>
<div class="wrapper">
<div class="buttonWrapper" *ngFor="let symbol of filteredSymbols | filenamefilter: searchText">
<button [ngClass]="{ buttonSelected: isSelected === symbol.symbol_id }" class="button" (click)="setSelected(symbol.symbol_id)">
<img [ngClass]="{ imageSelected: isSelected === symbol.symbol_id }" class="image" [src]="getImageSourceForTemplate(symbol.symbol_id)"/>
</button> </button>
</div> </div>
<form> </div>
<mat-form-field class="example-full-width">
<input matInput
placeholder="Search by filename"
[(ngModel)]="searchText"
[ngModelOptions]="{standalone: true}">
</mat-form-field>
</form>
<div class="wrapper">
<div class="buttonWrapper" *ngFor="let symbol of filteredSymbols | filenamefilter: searchText">
<button [ngClass]="{ buttonSelected: isSelected === symbol.symbol_id }" class="button" (click)="setSelected(symbol.symbol_id)">
<img [ngClass]="{ imageSelected: isSelected === symbol.symbol_id }" class="image" [src]="getImageSourceForTemplate(symbol.symbol_id)"/>
</button>
</div>
</div>
</mat-card>

View File

@ -7,23 +7,24 @@
<div class="default-content"> <div class="default-content">
<div class="container mat-elevation-z8"> <div class="container mat-elevation-z8">
<mat-vertical-stepper [linear]="true"> <mat-vertical-stepper [linear]="true">
<mat-step label="Server type"> <mat-step label="Server type" [completed]="isGns3VmChosen || isLocalComputerChosen">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<!-- <mat-radio-button class="radio-button" value="1" (click)="setServerType('remote computer')">Run this Docker container on a remote computer</mat-radio-button> --> <!-- <mat-radio-button class="radio-button" value="1" (click)="setServerType('remote computer')">Run this Docker container on a remote computer</mat-radio-button> -->
<mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run this Docker container on your local computer</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run this Docker container locally</mat-radio-button>
<mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run this Docker container on the GNS3 VM</mat-radio-button> <mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run this Docker container on the GNS3 VM</mat-radio-button>
</mat-radio-group> </mat-radio-group>
</mat-step> </mat-step>
<mat-step label="Docker Virtual Machine">
<mat-step label="Docker Virtual Machine" [completed]="selectedImage || virtualMachineForm.get('filename').value">
<form [formGroup]="virtualMachineForm"> <form [formGroup]="virtualMachineForm">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<mat-radio-button class="radio-button" value="1" (click)="setDiskImage('existingImage')" checked>Existing image</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setDiskImage('existingImage')" checked>Existing image</mat-radio-button>
<mat-radio-button class="radio-button" value="2" (click)="setDiskImage('newImage')">New image</mat-radio-button> <mat-radio-button class="radio-button" value="2" (click)="setDiskImage('newImage')">New image</mat-radio-button>
</mat-radio-group> </mat-radio-group>
<mat-select <mat-select
*ngIf="!newImageSelected" *ngIf="!newImageSelected"
placeholder="Image list" placeholder="Image list"
[ngModelOptions]="{standalone: true}" [ngModelOptions]="{standalone: true}"
[(ngModel)]="selectedImage"> [(ngModel)]="selectedImage">
<mat-option *ngFor="let image of dockerImages" [value]="image"> <mat-option *ngFor="let image of dockerImages" [value]="image">
{{image.image}} {{image.image}}
@ -41,7 +42,8 @@
</div> </div>
</form> </form>
</mat-step> </mat-step>
<mat-step label="Container name">
<mat-step label="Container name" [completed]="containerNameForm.get('templateName').value">
<form [formGroup]="containerNameForm"> <form [formGroup]="containerNameForm">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input <input
@ -53,7 +55,8 @@
</mat-form-field> </mat-form-field>
</form> </form>
</mat-step> </mat-step>
<mat-step label="Network adapters">
<mat-step label="Network adapters" [completed]="networkAdaptersForm.get('adapters').value">
<form [formGroup]="networkAdaptersForm"> <form [formGroup]="networkAdaptersForm">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input <input
@ -65,7 +68,8 @@
</mat-form-field> </mat-form-field>
</form> </form>
</mat-step> </mat-step>
<mat-step label="Start command">
<mat-step label="Start command" [completed]="dockerTemplate.start_command">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input <input
matInput matInput
@ -74,16 +78,18 @@
placeholder="Start command"/> placeholder="Start command"/>
</mat-form-field> </mat-form-field>
</mat-step> </mat-step>
<mat-step label="Console type">
<mat-select <mat-step label="Console type" [completed]="dockerTemplate.console_type">
placeholder="Console type" <mat-select
[ngModelOptions]="{standalone: true}" placeholder="Console type"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="dockerTemplate.console_type"> [(ngModel)]="dockerTemplate.console_type">
<mat-option *ngFor="let type of consoleTypes" [value]="type"> <mat-option *ngFor="let type of consoleTypes" [value]="type">
{{type}} {{type}}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-step> </mat-step>
<mat-step label="Environment"> <mat-step label="Environment">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<textarea matInput type="text" [(ngModel)]="dockerTemplate.environment"></textarea> <textarea matInput type="text" [(ngModel)]="dockerTemplate.environment"></textarea>

View File

@ -25,6 +25,7 @@ export class AddDockerTemplateComponent implements OnInit {
consoleTypes: string[] = []; consoleTypes: string[] = [];
isRemoteComputerChosen: boolean = false; isRemoteComputerChosen: boolean = false;
dockerImages: DockerImage[] = []; dockerImages: DockerImage[] = [];
selectedImage: DockerImage;
newImageSelected: boolean = false; newImageSelected: boolean = false;
virtualMachineForm: FormGroup; virtualMachineForm: FormGroup;
@ -49,15 +50,15 @@ export class AddDockerTemplateComponent implements OnInit {
this.dockerTemplate = new DockerTemplate(); this.dockerTemplate = new DockerTemplate();
this.virtualMachineForm = this.formBuilder.group({ this.virtualMachineForm = this.formBuilder.group({
filename: new FormControl('', Validators.required) filename: new FormControl(null, Validators.required)
}); });
this.containerNameForm = this.formBuilder.group({ this.containerNameForm = this.formBuilder.group({
templateName: new FormControl('', Validators.required) templateName: new FormControl(null, Validators.required)
}); });
this.networkAdaptersForm = this.formBuilder.group({ this.networkAdaptersForm = this.formBuilder.group({
adapters: new FormControl('', Validators.required) adapters: new FormControl('1', Validators.required)
}); });
} }
@ -101,11 +102,17 @@ export class AddDockerTemplateComponent implements OnInit {
} }
addTemplate() { addTemplate() {
if ((!this.virtualMachineForm.invalid || !this.newImageSelected) && !this.containerNameForm.invalid && !this.networkAdaptersForm.invalid) { if ((!this.virtualMachineForm.invalid || (!this.newImageSelected && this.selectedImage)) && !this.containerNameForm.invalid && !this.networkAdaptersForm.invalid) {
this.dockerTemplate.template_id = uuid(); this.dockerTemplate.template_id = uuid();
this.dockerTemplate.image = this.virtualMachineForm.get('filename').value;
if (this.newImageSelected) {
this.dockerTemplate.image = this.virtualMachineForm.get('filename').value;
} else {
this.dockerTemplate.image = this.selectedImage.image;
}
this.dockerTemplate.name = this.containerNameForm.get('templateName').value; this.dockerTemplate.name = this.containerNameForm.get('templateName').value;
this.dockerTemplate.adapters = this.networkAdaptersForm.get('adapters').value; this.dockerTemplate.adapters = +this.networkAdaptersForm.get('adapters').value;
this.dockerTemplate.compute_id = this.isGns3VmChosen ? 'vm' : 'local'; this.dockerTemplate.compute_id = this.isGns3VmChosen ? 'vm' : 'local';
this.dockerService.addTemplate(this.server, this.dockerTemplate).subscribe((template: DockerTemplate) => { this.dockerService.addTemplate(this.server, this.dockerTemplate).subscribe((template: DockerTemplate) => {

View File

@ -7,14 +7,14 @@
<div class="default-content" *ngIf="iosTemplate"> <div class="default-content" *ngIf="iosTemplate">
<div class="container mat-elevation-z8"> <div class="container mat-elevation-z8">
<mat-vertical-stepper [linear]="true"> <mat-vertical-stepper [linear]="true">
<mat-step label="Server type"> <mat-step label="Server type" [completed]="isGns3VmChosen || isLocalComputerChosen">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run this IOS router on your local computer</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run this IOS router locally</mat-radio-button>
<mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run this IOS router on the GNS3 VM</mat-radio-button> <mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run this IOS router on the GNS3 VM</mat-radio-button>
</mat-radio-group> </mat-radio-group>
</mat-step> </mat-step>
<mat-step label="IOS image"> <mat-step label="IOS image" [completed]="iosImageForm.get('imageName').value">
<input <input
type="file" type="file"
accept=".bin, .image" accept=".bin, .image"
@ -26,8 +26,8 @@
<button mat-raised-button color="primary" (click)="file.click()">Click to add image</button> or create from existing one <button mat-raised-button color="primary" (click)="file.click()">Click to add image</button> or create from existing one
<form [formGroup]="iosImageForm"> <form [formGroup]="iosImageForm">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<mat-select <mat-select
placeholder="Image" placeholder="Image"
(selectionChange)="onImageChosen($event)" (selectionChange)="onImageChosen($event)"
formControlName="imageName"> formControlName="imageName">
<mat-option *ngFor="let image of iosImages" [value]="image.filename"> <mat-option *ngFor="let image of iosImages" [value]="image.filename">
@ -37,17 +37,20 @@
</mat-form-field> </mat-form-field>
</form> </form>
</mat-step> </mat-step>
<mat-step label="Name and platform">
<mat-step
label="Name and platform"
[completed]="iosNameForm.get('templateName').value && iosNameForm.get('platform').value && iosNameForm.get('chassis').value">
<form [formGroup]="iosNameForm"> <form [formGroup]="iosNameForm">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input <input
matInput type="text" matInput type="text"
formControlName="templateName" formControlName="templateName"
placeholder="Name"/> placeholder="Name"/>
</mat-form-field> </mat-form-field>
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<mat-select <mat-select
placeholder="Platform" placeholder="Platform"
(selectionChange)="onPlatformChosen($event)" (selectionChange)="onPlatformChosen($event)"
formControlName="platform"> formControlName="platform">
<mat-option *ngFor="let platform of platforms" [value]="platform"> <mat-option *ngFor="let platform of platforms" [value]="platform">
@ -56,8 +59,8 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="form-field" *ngIf="chassis[iosNameForm.get('platform').value]"> <mat-form-field class="form-field" *ngIf="chassis[iosNameForm.get('platform').value]">
<mat-select <mat-select
placeholder="Chassis" placeholder="Chassis"
(selectionChange)="onChassisChosen($event)" (selectionChange)="onChassisChosen($event)"
formControlName="chassis"> formControlName="chassis">
<mat-option *ngFor="let chassis of chassis[iosNameForm.get('platform').value]" [value]="chassis"> <mat-option *ngFor="let chassis of chassis[iosNameForm.get('platform').value]" [value]="chassis">
@ -66,17 +69,18 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</form> </form>
<mat-checkbox <mat-checkbox
*ngIf="platformsWithEtherSwitchRouterOption[iosTemplate.platform]" *ngIf="platformsWithEtherSwitchRouterOption[iosTemplate.platform]"
[(ngModel)]="isEtherSwitchRouter"> [(ngModel)]="isEtherSwitchRouter">
This is an EtherSwitch router This is an EtherSwitch router
</mat-checkbox> </mat-checkbox>
</mat-step> </mat-step>
<mat-step label="Memory">
<mat-step label="Memory" [completed]="iosMemoryForm.get('memory').value">
<form [formGroup]="iosMemoryForm"> <form [formGroup]="iosMemoryForm">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input <input
matInput type="number" matInput type="number"
formControlName="memory" formControlName="memory"
value="defaultRam[iosNameForm.get('platform').value]" value="defaultRam[iosNameForm.get('platform').value]"
placeholder="Default RAM"/> placeholder="Default RAM"/>
@ -87,54 +91,57 @@
</mat-label> </mat-label>
</form> </form>
</mat-step> </mat-step>
<mat-step label="Network adapters"> <mat-step label="Network adapters">
<div *ngIf="chassis[iosNameForm.get('platform').value]"> <!-- <div *ngIf="chassis[iosNameForm.get('platform').value]">
<div *ngFor="let index of [0,1,2,3,4,5,6,7]"> <div *ngFor="let index of [0,1,2,3,4,5,6,7]">
<mat-select <mat-select
placeholder="Slot {{index}}" placeholder="Slot {{index}}"
[(ngModel)]="networkAdaptersForTemplate[index]" [(ngModel)]="networkAdaptersForTemplate[index]"
[ngModelOptions]="{standalone: true}" [ngModelOptions]="{standalone: true}"
*ngIf="networkAdapters[iosNameForm.get('chassis').value] && networkAdapters[iosNameForm.get('chassis').value][index]"> *ngIf="networkAdapters[iosNameForm.get('chassis').value] && networkAdapters[iosNameForm.get('chassis').value][index]">
<mat-option *ngFor="let option of networkAdapters[iosNameForm.get('chassis').value][index]" [value]="option"> <mat-option *ngFor="let option of networkAdapters[iosNameForm.get('chassis').value][index]" [value]="option">
{{option}} {{option}}
</mat-option> </mat-option>
</mat-select> </mat-select>
</div> </div>
</div> </div> -->
<div *ngIf="chassis[iosNameForm.get('platform').value]"> <div *ngIf="selectedPlatform">
<div *ngFor="let index of [0,1,2,3,4,5,6,7]"> <div *ngFor="let index of [0,1,2,3,4,5,6,7]">
<mat-select <mat-select
placeholder="Slot {{index}}" placeholder="Slot {{index}}"
[(ngModel)]="networkAdaptersForTemplate[index]" [(ngModel)]="networkAdaptersForTemplate[index]"
[ngModelOptions]="{standalone: true}" [ngModelOptions]="{standalone: true}"
*ngIf="networkAdaptersForPlatform[iosNameForm.get('platform').value] && networkAdaptersForPlatform[iosNameForm.get('platform').value][index]"> *ngIf="networkAdaptersForPlatform[iosNameForm.get('platform').value] && networkAdaptersForPlatform[iosNameForm.get('platform').value][index]">
<mat-option *ngFor="let option of networkAdaptersForPlatform[iosNameForm.get('platform').value][index]" [value]="option"> <mat-option *ngFor="let option of networkAdaptersForPlatform[iosNameForm.get('platform').value][index]" [value]="option">
{{option}} {{option}}
</mat-option> </mat-option>
</mat-select> </mat-select>
</div> </div>
</div> </div>
</mat-step> </mat-step>
<mat-step label="WIC modules"> <mat-step label="WIC modules">
<div *ngIf="iosNameForm.get('platform').value"> <div *ngIf="iosNameForm.get('platform').value">
<div *ngFor="let index of [0,1,2,3]"> <div *ngFor="let index of [0,1,2,3]">
<mat-select <mat-select
placeholder="WIC {{index}}" placeholder="WIC {{index}}"
[(ngModel)]="networkModulesForTemplate[index]" [(ngModel)]="networkModulesForTemplate[index]"
[ngModelOptions]="{standalone: true}" [ngModelOptions]="{standalone: true}"
*ngIf="networkModules[iosNameForm.get('platform').value] && networkModules[iosNameForm.get('platform').value][index]"> *ngIf="networkModules[iosNameForm.get('platform').value] && networkModules[iosNameForm.get('platform').value][index]">
<mat-option *ngFor="let option of networkModules[iosNameForm.get('platform').value][index]" [value]="option"> <mat-option *ngFor="let option of networkModules[iosNameForm.get('platform').value][index]" [value]="option">
{{option}} {{option}}
</mat-option> </mat-option>
</mat-select> </mat-select>
</div> </div>
</div> </div>
</mat-step> </mat-step>
<mat-step label="Idle-PC"> <mat-step label="Idle-PC">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input <input
matInput type="text" matInput type="text"
[(ngModel)]="iosTemplate.idlepc" [(ngModel)]="iosTemplate.idlepc"
placeholder="Idle-PC"/> placeholder="Idle-PC"/>
</mat-form-field> </mat-form-field>
</mat-step> </mat-step>

View File

@ -28,6 +28,7 @@ export class AddIosTemplateComponent implements OnInit {
iosImageForm: FormGroup; iosImageForm: FormGroup;
iosNameForm: FormGroup; iosNameForm: FormGroup;
iosMemoryForm: FormGroup; iosMemoryForm: FormGroup;
selectedPlatform: string;
networkAdaptersForTemplate: string[] = []; networkAdaptersForTemplate: string[] = [];
networkModulesForTemplate: string[] = []; networkModulesForTemplate: string[] = [];
@ -187,8 +188,8 @@ export class AddIosTemplateComponent implements OnInit {
} }
} }
} else { } else {
if(Object.keys(this.networkAdaptersForPlatform[this.iosTemplate.chassis])){ if(this.networkAdaptersForPlatform[this.iosNameForm.get('platform').value]){
for(let i=0; i<Object.keys(this.networkAdaptersForPlatform[this.iosTemplate.chassis]).length; i++){ for(let i=0; i<Object.keys(this.networkAdaptersForPlatform[this.iosNameForm.get('platform').value]).length; i++){
if(!this.networkAdaptersForTemplate[i]) this.networkAdaptersForTemplate[i] = ''; if(!this.networkAdaptersForTemplate[i]) this.networkAdaptersForTemplate[i] = '';
} }
} }
@ -224,11 +225,23 @@ export class AddIosTemplateComponent implements OnInit {
let name: string = this.iosImageForm.get("imageName").value.split('-')[0]; let name: string = this.iosImageForm.get("imageName").value.split('-')[0];
this.iosNameForm.controls['templateName'].setValue(name); this.iosNameForm.controls['templateName'].setValue(name);
if (this.platforms.includes(name.split('-')[0])) { if (name === 'c3620' || name === 'c3640' || name === 'c3660') {
this.iosNameForm.controls['platform'].setValue('c3600');
this.selectedPlatform = 'c3600';
} else {
this.iosNameForm.controls['platform'].setValue(name); this.iosNameForm.controls['platform'].setValue(name);
this.iosNameForm.controls['chassis'].setValue(''); this.selectedPlatform = name;
this.iosMemoryForm.controls['memory'].setValue(this.defaultRam[name]);
} }
if (name === 'c1700') {
this.iosNameForm.controls['chassis'].setValue('1720');
} else if (name === 'c2600') {
this.iosNameForm.controls['chassis'].setValue('2610');
} else {
this.iosNameForm.controls['chassis'].setValue('');
}
this.iosMemoryForm.controls['memory'].setValue(this.defaultRam[name]);
} }
onPlatformChosen() { onPlatformChosen() {

View File

@ -1,7 +1,7 @@
<div class="content"> <div class="content">
<div class="default-header"> <div class="default-header">
<div class="row"> <div class="row">
<h1 class="col">IOS routers templates</h1> <h1 class="col">IOS router templates</h1>
<button *ngIf="server" class="top-button" class="cancel-button" routerLink="/server/{{server.id}}/preferences" mat-button>Back</button> <button *ngIf="server" class="top-button" class="cancel-button" routerLink="/server/{{server.id}}/preferences" mat-button>Back</button>
<button *ngIf="server" class="top-button" routerLink="/server/{{server.id}}/preferences/dynamips/templates/addtemplate" mat-raised-button color="primary">Add IOS router template</button> <button *ngIf="server" class="top-button" routerLink="/server/{{server.id}}/preferences/dynamips/templates/addtemplate" mat-raised-button color="primary">Add IOS router template</button>
</div> </div>

View File

@ -7,19 +7,21 @@
<div class="default-content"> <div class="default-content">
<div class="container mat-elevation-z8"> <div class="container mat-elevation-z8">
<mat-vertical-stepper [linear]="true"> <mat-vertical-stepper [linear]="true">
<mat-step label="Server type"> <mat-step label="Server type" [completed]="isGns3VmChosen || isLocalComputerChosen">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run this IOU device on your local computer</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run this IOU device locally</mat-radio-button>
<mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run this IOU device on the GNS3 VM</mat-radio-button> <mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run this IOU device on the GNS3 VM</mat-radio-button>
</mat-radio-group> </mat-radio-group>
</mat-step> </mat-step>
<mat-step label="Name">
<mat-step label="Name" [completed]="templateNameForm.get('templateName').value">
<form [formGroup]="templateNameForm"> <form [formGroup]="templateNameForm">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input matInput placeholder="Name" type="text" formControlName="templateName"/> <input matInput placeholder="Name" type="text" formControlName="templateName"/>
</mat-form-field> </mat-form-field>
</form> </form>
</mat-step> </mat-step>
<mat-step label="Image"> <mat-step label="Image">
<form [formGroup]="imageForm"> <form [formGroup]="imageForm">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
@ -27,8 +29,8 @@
<mat-radio-button class="radio-button" value="2" (click)="setDiskImage('newImage')">New image</mat-radio-button> <mat-radio-button class="radio-button" value="2" (click)="setDiskImage('newImage')">New image</mat-radio-button>
</mat-radio-group> </mat-radio-group>
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<mat-select <mat-select
placeholder="Type" placeholder="Type"
[(ngModel)]="selectedType" [(ngModel)]="selectedType"
[ngModelOptions]="{standalone: true}"> [ngModelOptions]="{standalone: true}">
<mat-option *ngFor="let type of types" [value]="type"> <mat-option *ngFor="let type of types" [value]="type">
@ -37,8 +39,8 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="form-field" *ngIf="!newImageSelected"> <mat-form-field class="form-field" *ngIf="!newImageSelected">
<mat-select <mat-select
placeholder="IOU image" placeholder="IOU image"
[(ngModel)]="iouTemplate.path" [(ngModel)]="iouTemplate.path"
[ngModelOptions]="{standalone: true}"> [ngModelOptions]="{standalone: true}">
<mat-option *ngFor="let image of iouImages" [value]="image.path"> <mat-option *ngFor="let image of iouImages" [value]="image.path">

View File

@ -49,7 +49,7 @@ export class AddIouTemplateComponent implements OnInit {
this.iouTemplate = new IouTemplate(); this.iouTemplate = new IouTemplate();
this.templateNameForm = this.formBuilder.group({ this.templateNameForm = this.formBuilder.group({
templateName: new FormControl('', Validators.required) templateName: new FormControl(null, Validators.required)
}); });
this.imageForm = this.formBuilder.group({ this.imageForm = this.formBuilder.group({
@ -133,6 +133,14 @@ export class AddIouTemplateComponent implements OnInit {
this.iouTemplate.name = this.templateNameForm.get("templateName").value; this.iouTemplate.name = this.templateNameForm.get("templateName").value;
if (this.newImageSelected) this.iouTemplate.path = this.imageForm.get("imageName").value; if (this.newImageSelected) this.iouTemplate.path = this.imageForm.get("imageName").value;
this.iouTemplate.compute_id = this.isGns3VmChosen ? 'vm' : 'local'; this.iouTemplate.compute_id = this.isGns3VmChosen ? 'vm' : 'local';
if (this.selectedType === 'L2 image') {
this.iouTemplate.ethernet_adapters = 4;
this.iouTemplate.serial_adapters = 0;
} else if (this.selectedType === 'L3 image') {
this.iouTemplate.ethernet_adapters = 2;
this.iouTemplate.serial_adapters = 2;
}
this.iouService.addTemplate(this.server, this.iouTemplate).subscribe((template: IouTemplate) => { this.iouService.addTemplate(this.server, this.iouTemplate).subscribe((template: IouTemplate) => {
this.goBack(); this.goBack();

View File

@ -1,7 +1,7 @@
<div class="content"> <div class="content">
<div class="default-header"> <div class="default-header">
<div class="row"> <div class="row">
<h1 class="col">IOU devices templates</h1> <h1 class="col">IOU device templates</h1>
<button *ngIf="server" class="top-button" class="cancel-button" routerLink="/server/{{server.id}}/preferences" mat-button>Back</button> <button *ngIf="server" class="top-button" class="cancel-button" routerLink="/server/{{server.id}}/preferences" mat-button>Back</button>
<button *ngIf="server" class="top-button" routerLink="/server/{{server.id}}/preferences/iou/addtemplate" mat-raised-button color="primary">Add IOU device template</button> <button *ngIf="server" class="top-button" routerLink="/server/{{server.id}}/preferences/iou/addtemplate" mat-raised-button color="primary">Add IOU device template</button>
</div> </div>

View File

@ -7,31 +7,29 @@
<div class="default-content"> <div class="default-content">
<div class="container mat-elevation-z8"> <div class="container mat-elevation-z8">
<mat-vertical-stepper [linear]="true"> <mat-vertical-stepper [linear]="true">
<mat-step label="Server type"> <mat-step label="Server type" [completed]="isGns3VmChosen || isLocalComputerChosen">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run this QEMU VM on your local computer</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run this QEMU VM locally</mat-radio-button>
<mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run this QEMU VM on the GNS3 VM</mat-radio-button> <mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run this QEMU VM on the GNS3 VM</mat-radio-button>
</mat-radio-group> </mat-radio-group>
</mat-step> </mat-step>
<mat-step label="QEMU VM Name"> <mat-step label="QEMU VM Name" [completed]="nameForm.get('templateName').value">
<form [formGroup]="nameForm"> <form [formGroup]="nameForm">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input <input
matInput type="text" matInput type="text"
formControlName="templateName" formControlName="templateName"
placeholder="Please choose a descriptive name for your new QEMU virtual machine" placeholder="Please choose a descriptive name for your new QEMU virtual machine"
ngDefaultContro/> ngDefaultContro/>
</mat-form-field><br/> </mat-form-field><br/>
<mat-checkbox>
This is a legacy ASA VM
</mat-checkbox>
</form> </form>
</mat-step> </mat-step>
<mat-step label="QEMU binary and memory">
<mat-step label="QEMU binary and memory" [completed]="memoryForm.get('ramMemory').value && selectedBinary">
<form [formGroup]="memoryForm"> <form [formGroup]="memoryForm">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<mat-select <mat-select
placeholder="Qemu binary" placeholder="Qemu binary"
[(ngModel)]="selectedBinary" [(ngModel)]="selectedBinary"
[ngModelOptions]="{standalone: true}"> [ngModelOptions]="{standalone: true}">
@ -41,19 +39,20 @@
</mat-select> </mat-select>
</mat-form-field><br/> </mat-form-field><br/>
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<input <input
matInput type="number" matInput type="number"
placeholder="RAM" placeholder="RAM"
formControlName="ramMemory" formControlName="ramMemory"
ngDefaultContro/> ngDefaultControl/>
<span matSuffix>MB</span> <span matSuffix>MB</span>
</mat-form-field> </mat-form-field>
</form> </form>
</mat-step> </mat-step>
<mat-step label="Console type">
<mat-step label="Console type" [completed]="qemuTemplate.console_type">
<mat-form-field class="form-field"> <mat-form-field class="form-field">
<mat-select <mat-select
placeholder="Console type" placeholder="Console type"
[(ngModel)]="qemuTemplate.console_type" [(ngModel)]="qemuTemplate.console_type"
[ngModelOptions]="{standalone: true}" > [ngModelOptions]="{standalone: true}" >
<mat-option *ngFor="let type of consoleTypes" [value]="type"> <mat-option *ngFor="let type of consoleTypes" [value]="type">
@ -62,16 +61,17 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</mat-step> </mat-step>
<mat-step label="Disk image"> <mat-step label="Disk image">
<form [formGroup]="diskForm"> <form [formGroup]="diskForm">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<mat-radio-button class="radio-button" value="1" (click)="setDiskImage('existingImage')" checked>Existing image</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setDiskImage('existingImage')" checked>Existing image</mat-radio-button>
<mat-radio-button class="radio-button" value="2" (click)="setDiskImage('newImage')">New image</mat-radio-button> <mat-radio-button class="radio-button" value="2" (click)="setDiskImage('newImage')">New image</mat-radio-button>
</mat-radio-group><br/><br/> </mat-radio-group><br/><br/>
<mat-select <mat-select
*ngIf="!newImageSelected" *ngIf="!newImageSelected"
placeholder="Disk image (hda)" placeholder="Disk image (hda)"
[ngModelOptions]="{standalone: true}" [ngModelOptions]="{standalone: true}"
[(ngModel)]="selectedImage"> [(ngModel)]="selectedImage">
<mat-option *ngFor="let image of qemuImages" [value]="image"> <mat-option *ngFor="let image of qemuImages" [value]="image">
{{image.filename}} {{image.filename}}

View File

@ -56,11 +56,11 @@ export class AddQemuVmTemplateComponent implements OnInit {
this.qemuTemplate = new QemuTemplate(); this.qemuTemplate = new QemuTemplate();
this.nameForm = this.formBuilder.group({ this.nameForm = this.formBuilder.group({
templateName: new FormControl('', Validators.required) templateName: new FormControl(null, Validators.required)
}); });
this.memoryForm = this.formBuilder.group({ this.memoryForm = this.formBuilder.group({
ramMemory: new FormControl('', Validators.required) ramMemory: new FormControl('256', Validators.required)
}); });
this.diskForm = this.formBuilder.group({ this.diskForm = this.formBuilder.group({
@ -98,6 +98,7 @@ export class AddQemuVmTemplateComponent implements OnInit {
this.qemuService.getBinaries(server).subscribe((qemuBinaries: QemuBinary[]) => { this.qemuService.getBinaries(server).subscribe((qemuBinaries: QemuBinary[]) => {
this.qemuBinaries = qemuBinaries; this.qemuBinaries = qemuBinaries;
if (this.qemuBinaries[0]) this.selectedBinary = this.qemuBinaries[0];
}); });
this.qemuService.getImages(server).subscribe((qemuImages: QemuImage[]) => { this.qemuService.getImages(server).subscribe((qemuImages: QemuImage[]) => {

View File

@ -7,7 +7,7 @@
<div class="default-content"> <div class="default-content">
<mat-card class="matCard"> <mat-card class="matCard">
<mat-radio-group class="radio-group"> <mat-radio-group class="radio-group">
<mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run the VPCS node on your local computer</mat-radio-button> <mat-radio-button class="radio-button" value="1" (click)="setServerType('local')" checked>Run the VPCS node locally</mat-radio-button>
<mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run the VPCS node on the GNS3 VM</mat-radio-button> <mat-radio-button [disabled]="!isGns3VmAvailable" class="radio-button" value="2" (click)="setServerType('gns3 vm')">Run the VPCS node on the GNS3 VM</mat-radio-button>
</mat-radio-group> </mat-radio-group>

View File

@ -0,0 +1,24 @@
<h1 mat-dialog-title>Change hostname for node {{name}}</h1>
<div class="modal-form-container">
<div class="content">
<div class="default-content">
<mat-card class="matCard">
<form [formGroup]="inputForm">
<mat-form-field class="form-field">
<input
matInput type="text"
[(ngModel)]="node.name"
formControlName="name"
placeholder="Name">
</mat-form-field>
</form>
</mat-card>
</div>
</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>

View File

@ -0,0 +1,3 @@
.form-field {
width: 100%;
}

View File

@ -0,0 +1,53 @@
import { Component, OnInit, Input } 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';
@Component({
selector: 'app-change-hostname-dialog-component',
templateUrl: './change-hostname-dialog.component.html',
styleUrls: ['./change-hostname-dialog.component.scss']
})
export class ChangeHostnameDialogComponent implements OnInit {
server: Server;
node: Node;
inputForm: FormGroup;
name: string;
constructor(
public dialogRef: MatDialogRef<ChangeHostnameDialogComponent>,
public nodeService: NodeService,
private toasterService: ToasterService,
private formBuilder: FormBuilder
) {
this.inputForm = this.formBuilder.group({
name: new FormControl('', Validators.required)
});
}
ngOnInit() {
this.nodeService.getNode(this.server, this.node).subscribe((node: Node) => {
this.node = node;
this.name = this.node.name;
})
}
onSaveClick() {
if (this.inputForm.valid) {
this.nodeService.updateNode(this.server, this.node).subscribe(() => {
this.toasterService.success(`Node ${this.node.name} updated.`);
this.onCancelClick();
});
} else {
this.toasterService.error(`Fill all required fields.`);
}
}
onCancelClick() {
this.dialogRef.close();
}
}

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="changeHostname()">
<mat-icon>edit</mat-icon>
<span>Change hostname</span>
</button>

View File

@ -0,0 +1,28 @@
import { Component, OnInit, Input } from '@angular/core';
import { Server } from '../../../../../models/server';
import { Node } from '../../../../../cartography/models/node';
import { MatDialog } from '@angular/material';
import { ChangeHostnameDialogComponent } from '../../../change-hostname-dialog/change-hostname-dialog.component';
@Component({
selector: 'app-change-hostname-action',
templateUrl: './change-hostname-action.component.html'
})
export class ChangeHostnameActionComponent implements OnInit {
@Input() server: Server;
@Input() node: Node;
constructor(private dialog: MatDialog) {}
ngOnInit() {}
changeHostname() {
const dialogRef = this.dialog.open(ChangeHostnameDialogComponent, {
autoFocus: false,
disableClose: true
});
let instance = dialogRef.componentInstance;
instance.server = this.server;
instance.node = this.node;
}
}

View File

@ -1,4 +1,4 @@
<button mat-menu-item (click)="changeSymbol()"> <button mat-menu-item (click)="changeSymbol()">
<mat-icon>edit</mat-icon> <mat-icon>find_replace</mat-icon>
<span>Change symbol</span> <span>Change symbol</span>
</button> </button>

View File

@ -40,6 +40,11 @@
[server]="server" [server]="server"
[node]="nodes[0]" [node]="nodes[0]"
></app-open-file-explorer-action> ></app-open-file-explorer-action>
<app-change-hostname-action
*ngIf="!projectService.isReadOnly(project) && nodes.length===1"
[server]="server"
[node]="nodes[0]"
></app-change-hostname-action>
<app-change-symbol-action <app-change-symbol-action
*ngIf="!projectService.isReadOnly(project) && nodes.length===1" *ngIf="!projectService.isReadOnly(project) && nodes.length===1"
[server]="server" [server]="server"

View File

@ -155,6 +155,9 @@
<br/><mat-checkbox [(ngModel)]="node.properties.legacy_networking"> <br/><mat-checkbox [(ngModel)]="node.properties.legacy_networking">
Use the legacy networking mode Use the legacy networking mode
</mat-checkbox> </mat-checkbox>
<br/><mat-checkbox [(ngModel)]="node.properties.replicate_network_connection_state">
Replicate network connection state
</mat-checkbox>
<app-custom-adapters-table <app-custom-adapters-table
#customAdapters #customAdapters
[networkTypes]="networkTypes" [networkTypes]="networkTypes"

View File

@ -68,6 +68,7 @@ import { NotificationService } from '../../services/notification.service';
import { ThemeService } from '../../services/theme.service'; import { ThemeService } from '../../services/theme.service';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { NewTemplateDialogComponent } from './new-template-dialog/new-template-dialog.component'; import { NewTemplateDialogComponent } from './new-template-dialog/new-template-dialog.component';
import { NodeConsoleService } from '../../services/nodeConsole.service';
@Component({ @Component({
@ -157,7 +158,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private bottomSheet: MatBottomSheet, private bottomSheet: MatBottomSheet,
private notificationService: NotificationService, private notificationService: NotificationService,
private themeService: ThemeService, private themeService: ThemeService,
private title: Title private title: Title,
private nodeConsoleService: NodeConsoleService
) {} ) {}
ngOnInit() { ngOnInit() {
@ -435,6 +437,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
if(!nodeAddedEvent) { if(!nodeAddedEvent) {
return; return;
} }
this.progressService.activate();
this.nodeService.createFromTemplate(this.server, this.project, nodeAddedEvent.template, nodeAddedEvent.x, nodeAddedEvent.y, nodeAddedEvent.server).subscribe((node: Node) => { this.nodeService.createFromTemplate(this.server, this.project, nodeAddedEvent.template, nodeAddedEvent.x, nodeAddedEvent.y, nodeAddedEvent.server).subscribe((node: Node) => {
// if (nodeAddedEvent.name !== nodeAddedEvent.template.name) { // if (nodeAddedEvent.name !== nodeAddedEvent.template.name) {
// node.name = nodeAddedEvent.name; // node.name = nodeAddedEvent.name;
@ -453,6 +457,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
nodeAddedEvent.x = nodeAddedEvent.x + 50 < this.project.scene_width/2 ? nodeAddedEvent.x + 50 : nodeAddedEvent.x; nodeAddedEvent.x = nodeAddedEvent.x + 50 < this.project.scene_width/2 ? nodeAddedEvent.x + 50 : nodeAddedEvent.x;
nodeAddedEvent.y = nodeAddedEvent.y + 50 < this.project.scene_height/2 ? nodeAddedEvent.y + 50 : nodeAddedEvent.y; nodeAddedEvent.y = nodeAddedEvent.y + 50 < this.project.scene_height/2 ? nodeAddedEvent.y + 50 : nodeAddedEvent.y;
this.onNodeCreation(nodeAddedEvent); this.onNodeCreation(nodeAddedEvent);
} else {
this.progressService.deactivate();
} }
}); });
}); });
@ -864,7 +870,9 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
} }
public ngOnDestroy() { public ngOnDestroy() {
this.nodeConsoleService.openConsoles = 0;
this.title.setTitle('GNS3 Web UI'); this.title.setTitle('GNS3 Web UI');
this.drawingsDataSource.clear(); this.drawingsDataSource.clear();
this.nodesDataSource.clear(); this.nodesDataSource.clear();
this.linksDataSource.clear(); this.linksDataSource.clear();

View File

@ -33,7 +33,7 @@
Start all nodes when this project is opened Start all nodes when this project is opened
</mat-checkbox> </mat-checkbox>
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="!project.auto_close"> <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="auto_close">
Leave this project running in the background after closing Leave this project running in the background after closing
</mat-checkbox> </mat-checkbox>

View File

@ -22,6 +22,8 @@ export class EditProjectDialogComponent implements OnInit {
displayedColumns: string[] = ['name', 'value', 'actions']; displayedColumns: string[] = ['name', 'value', 'actions'];
variables: ProjectVariable[] = []; variables: ProjectVariable[] = [];
auto_close: boolean;
constructor( constructor(
public dialogRef: MatDialogRef<EditProjectDialogComponent>, public dialogRef: MatDialogRef<EditProjectDialogComponent>,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
@ -52,6 +54,7 @@ export class EditProjectDialogComponent implements OnInit {
if (this.project.variables) { if (this.project.variables) {
this.project.variables.forEach(n => this.variables.push(n)); this.project.variables.forEach(n => this.variables.push(n));
} }
this.auto_close = !this.project.auto_close;
} }
addVariable() { addVariable() {
@ -83,6 +86,8 @@ export class EditProjectDialogComponent implements OnInit {
this.project.grid_size = this.formGroup.get('nodeGridSize').value; this.project.grid_size = this.formGroup.get('nodeGridSize').value;
this.project.variables = this.variables; this.project.variables = this.variables;
this.project.auto_close = !this.project.auto_close;
this.projectService.update(this.server, this.project).subscribe((project: Project) => { this.projectService.update(this.server, this.project).subscribe((project: Project) => {
this.toasterService.success(`Project ${project.name} updated.`); this.toasterService.success(`Project ${project.name} updated.`);
this.onNoClick(); this.onNoClick();

View File

@ -57,6 +57,7 @@ export class AddServerDialogComponent implements OnInit {
async numberOfLocalServers() { async numberOfLocalServers() {
const servers = await this.serverService.findAll(); const servers = await this.serverService.findAll();
return servers.filter((server) => server.location === 'local').length; return servers.filter((server) => server.location === 'local').length;
} }

View File

@ -32,7 +32,11 @@ export class ServerDiscoveryComponent implements OnInit {
) {} ) {}
ngOnInit() { ngOnInit() {
this.discoverFirstAvailableServer(); this.serverService.serviceInitialized.subscribe(async (value: boolean) => {
if (value) {
this.discoverFirstAvailableServer();
}
});
} }
discoverFirstAvailableServer() { discoverFirstAvailableServer() {

View File

@ -40,24 +40,28 @@ export class ServersComponent implements OnInit, OnDestroy {
this.isElectronApp = this.electronService.isElectronApp; this.isElectronApp = this.electronService.isElectronApp;
const runningServersNames = this.serverManagement.getRunningServers(); const runningServersNames = this.serverManagement.getRunningServers();
this.serverService.findAll().then((servers: Server[]) => { this.serverService.serviceInitialized.subscribe(async (value: boolean) => {
servers.forEach((server) => { if (value) {
const serverIndex = runningServersNames.findIndex((serverName) => server.name === serverName); this.serverService.findAll().then((servers: Server[]) => {
if(serverIndex >= 0) { servers.forEach((server) => {
server.status = 'running'; const serverIndex = runningServersNames.findIndex((serverName) => server.name === serverName);
} if(serverIndex >= 0) {
}); server.status = 'running';
servers.forEach((server) => {
this.serverService.checkServerVersion(server).subscribe(
(serverInfo) => {
if ((serverInfo.version.split('.')[1]>=2) && (serverInfo.version.split('.')[0]>=2)) {
if (!this.serverDatabase.find(server.name)) this.serverDatabase.addServer(server);
} }
}, });
error => {}
); servers.forEach((server) => {
}); this.serverService.checkServerVersion(server).subscribe(
(serverInfo) => {
if ((serverInfo.version.split('.')[1]>=2) && (serverInfo.version.split('.')[0]>=2)) {
if (!this.serverDatabase.find(server.name)) this.serverDatabase.addServer(server);
}
},
error => {}
);
});
});
}
}); });
this.dataSource = new ServerDataSource(this.serverDatabase); this.dataSource = new ServerDataSource(this.serverDatabase);

View File

@ -62,7 +62,10 @@ export class IosConfigurationService {
"c1700": 128, "c1700": 128,
"c2600": 128, "c2600": 128,
"c2691": 256, "c2691": 256,
"c3600": 256, "c3600": 192,
"c3620": 192,
"c3640": 192,
"c3660": 192,
"c3725": 256, "c3725": 256,
"c3745": 256, "c3745": 256,
"c7200": 512 "c7200": 512
@ -216,6 +219,33 @@ export class IosConfigurationService {
} }
getNetworkAdaptersForPlatform() { getNetworkAdaptersForPlatform() {
let networkAdaptersForPlatform = {};
networkAdaptersForPlatform["c2691"] = {
0: ["GT96100-FE"],
1: this.c3700_nms
};
networkAdaptersForPlatform["c3725"] = {
0: ["GT96100-FE"],
1: this.c3700_nms,
2: this.c3700_nms
};
networkAdaptersForPlatform["c3745"] = {
0: ["GT96100-FE"],
1: this.c3700_nms,
2: this.c3700_nms,
3: this.c3700_nms,
4: this.c3700_nms
};
networkAdaptersForPlatform["c7200"] = {
0: this.c7200_io,
1: this.c7200_pas,
2: this.c7200_pas,
3: this.c7200_pas,
4: this.c7200_pas,
5: this.c7200_pas,
6: this.c7200_pas
};
return { return {
"c2691": { "c2691": {
0: ["GT96100-FE"], 0: ["GT96100-FE"],

View File

@ -1,28 +1,58 @@
import { Injectable } from '@angular/core'; import { Injectable, EventEmitter } from '@angular/core';
import { IndexedDbService } from './indexed-db.service'; import { IndexedDbService } from './indexed-db.service';
import { Server } from '../models/server'; import { Server } from '../models/server';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { HttpServer } from './http-server.service'; import { HttpServer } from './http-server.service';
import { ToasterService } from './toaster.service';
@Injectable() @Injectable()
export class ServerService { export class ServerService {
private tablename = 'servers'; private tablename = 'servers';
private ready: Promise<any>; private ready: Promise<any>;
private isIncognitoMode: boolean = false;
private serverIdsInIncognitoMode: string[] = [];
public serviceInitialized: EventEmitter<boolean> = new EventEmitter<boolean>();
constructor( constructor(
private indexedDbService: IndexedDbService, private indexedDbService: IndexedDbService,
private httpServer: HttpServer private httpServer: HttpServer,
private toasterService: ToasterService
) { ) {
this.ready = indexedDbService.get().openDatabase(1, evt => { this.indexedDbService.get().openDatabase(1).then(() => {
evt.currentTarget.result.createObjectStore(this.tablename, { keyPath: 'id', autoIncrement: true }); this.ready = indexedDbService.get().openDatabase(1, evt => {
evt.currentTarget.result.createObjectStore(this.tablename, { keyPath: 'id', autoIncrement: true });
});
}).catch(() => {
this.isIncognitoMode = true;
}).finally(() => {
this.serviceInitialized.emit(true);
}); });
} }
public get(id: number): Promise<Server> { public get(id: number): Promise<Server> {
if (this.isIncognitoMode) {
let server: Server = JSON.parse(localStorage.getItem(`server-${id}`));
let promise = new Promise<Server>(resolve => {
resolve(server);
});
return promise;
}
return this.onReady(() => this.indexedDbService.get().getByKey(this.tablename, id)) as Promise<Server>; return this.onReady(() => this.indexedDbService.get().getByKey(this.tablename, id)) as Promise<Server>;
} }
public create(server: Server) { public create(server: Server) {
if (this.isIncognitoMode) {
server.id = this.serverIdsInIncognitoMode.length + 1;
localStorage.setItem(`server-${server.id}`, JSON.stringify(server));
this.serverIdsInIncognitoMode.push(`server-${server.id}`);
let promise = new Promise<Server>(resolve => {
resolve(server);
});
return promise;
}
return this.onReady(() => { return this.onReady(() => {
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
this.indexedDbService this.indexedDbService
@ -38,6 +68,16 @@ export class ServerService {
} }
public update(server: Server) { public update(server: Server) {
if (this.isIncognitoMode) {
localStorage.removeItem(`server-${server.id}`);
localStorage.setItem(`server-${server.id}`, JSON.stringify(server));
let promise = new Promise<Server>(resolve => {
resolve(server);
});
return promise;
}
return this.onReady(() => { return this.onReady(() => {
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
this.indexedDbService this.indexedDbService
@ -52,10 +92,32 @@ export class ServerService {
} }
public findAll() { public findAll() {
if (this.isIncognitoMode) {
let promise = new Promise<Server[]>(resolve => {
let servers: Server[] = [];
this.serverIdsInIncognitoMode.forEach(n => {
let server: Server = JSON.parse(localStorage.getItem(n));
servers.push(server);
});
resolve(servers);
});
return promise;
}
return this.onReady(() => this.indexedDbService.get().getAll(this.tablename)) as Promise<Server[]>; return this.onReady(() => this.indexedDbService.get().getAll(this.tablename)) as Promise<Server[]>;
} }
public delete(server: Server) { public delete(server: Server) {
if (this.isIncognitoMode) {
localStorage.removeItem(`server-${server.id}`);
this.serverIdsInIncognitoMode = this.serverIdsInIncognitoMode.filter(n => n !== `server-${server.id}`);
let promise = new Promise(resolve => {
resolve(server.id);
});
return promise;
}
return this.onReady(() => this.indexedDbService.get().delete(this.tablename, server.id)); return this.onReady(() => this.indexedDbService.get().delete(this.tablename, server.id));
} }

View File

@ -229,7 +229,7 @@ export class TemplateMocksService {
headless: false, headless: false,
linked_clone: false, linked_clone: false,
name: '', name: '',
on_close: 'power-off', on_close: 'power_off',
port_name_format: 'Ethernet{0}', port_name_format: 'Ethernet{0}',
port_segment_size: 0, port_segment_size: 0,
symbol: ':/symbols/vmware_guest.svg', symbol: ':/symbols/vmware_guest.svg',