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",
"version": "2020.2.0-beta.3",
"version": "2020.2.0-beta.4",
"author": {
"name": "GNS3 Technology Inc.",
"email": "developers@gns3.com"

View File

@ -1,6 +1,19 @@
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
- Drag & drop to add new nodes on topology

View File

@ -169,7 +169,7 @@ const routes: Routes = [
];
@NgModule({
imports: [RouterModule.forRoot(routes, { anchorScrolling: 'enabled', enableTracing: true, scrollPositionRestoration: 'enabled'})],
imports: [RouterModule.forRoot(routes, { anchorScrolling: 'enabled', enableTracing: false, scrollPositionRestoration: 'enabled'})],
exports: [RouterModule]
})
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 { ApplianceService } from './services/appliances.service';
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) {
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
@ -466,7 +468,9 @@ if (environment.production) {
ConsoleWrapperComponent,
HttpConsoleNewTabActionComponent,
WebConsoleFullWindowComponent,
NewTemplateDialogComponent
NewTemplateDialogComponent,
ChangeHostnameActionComponent,
ChangeHostnameDialogComponent
],
imports: [
BrowserModule,
@ -607,7 +611,8 @@ if (environment.production) {
ConfirmationBottomSheetComponent,
ConfigDialogComponent,
AdbutlerComponent,
NewTemplateDialogComponent
NewTemplateDialogComponent,
ChangeHostnameDialogComponent
],
bootstrap: [AppComponent]
})

View File

@ -18,21 +18,21 @@ export class StylesToFontConverter implements Converter<string, Font> {
});
ast.children.forEach(child => {
if (child.property === 'font-size') {
if (child.property === 'font-size' && child.value && child.value.children) {
child.value.children.forEach(value => {
if (value.type === 'Dimension') {
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 => {
if (value.type === 'Identifier') {
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 => {
if (value.type === 'Identifier') {
font.font_weight = value.name;

View File

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

View File

@ -30,7 +30,7 @@ export class FontFixer {
let isByIdentifier = true;
ast.children.forEach(child => {
if (child.property === 'font-family') {
if (child.property === 'font-family' && child.value && child.value.children) {
child.value.children.forEach(value => {
if (value.type === 'Identifier') {
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 => {
if (value.type === 'Dimension') {
fontSizePointer = value;

View File

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

View File

@ -87,12 +87,14 @@ export class NodeWidget implements Widget {
})
.attr('xnode:href', (n: MapNode) => n.symbolUrl)
.attr('width', (n: MapNode) => {
if (!n.width) return 60
return n.width
if (!n.width) return 60;
if (n.width > 64) return 64;
return n.width;
})
.attr('height', (n: MapNode) => {
if (!n.height) return 60
return n.height
if (!n.height) return 60;
if (n.height > 64) return 64;
return n.height;
})
.attr('x', (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 {
handleError(err: any): void {
super.handleError(err);
if (!err) return;
const toasterService = this.injector.get(ToasterService);
if (err.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 serverPort = +this.route.snapshot.paramMap.get('server_port');
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();
const server = servers.filter(server => server.host === serverIp && server.port === serverPort)[0];
if (server) {
this.router.navigate(['/server', server.id, 'project', projectId]);
} else {
let serverToAdd: Server = new Server();
serverToAdd.host = serverIp;
serverToAdd.port = serverPort;
serverToAdd.location = 'bundled';
serverToAdd.name = serverIp;
this.serverService.create(serverToAdd).then((addedServer: Server) => {
this.router.navigate(['/server', addedServer.id, 'project', projectId]);
});
}
if (server) {
this.router.navigate(['/server', server.id, 'project', projectId]);
} else {
let serverToAdd: Server = new Server();
serverToAdd.host = serverIp;
serverToAdd.port = serverPort;
serverToAdd.location = 'bundled';
serverToAdd.name = serverIp;
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">
<mat-card class="matCard">
<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-group>

View File

@ -7,7 +7,7 @@
<div class="default-content">
<mat-card class="matCard">
<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-group>

View File

@ -7,10 +7,10 @@
<div class="default-content">
<mat-card class="matCard">
<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-group>
<form [formGroup]="formGroup">
<mat-form-field class="form-field">
<input formControlName="templateName" matInput type="text" placeholder="Template name">

View File

@ -1,34 +1,32 @@
<mat-card>
<div class="menu">
<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="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-group>
<input
type="file"
accept=".svg, .bmp, .jpeg, .jpg, .gif, .png"
class="non-visible"
#file
(change)="uploadSymbolFile($event)"/>
<button mat-button (click)="file.click()">
<mat-icon>add</mat-icon>
Add symbol
<div class="menu">
<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="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-group>
<input
type="file"
accept=".svg, .bmp, .jpeg, .jpg, .gif, .png"
class="non-visible"
#file
(change)="uploadSymbolFile($event)"/>
<button mat-button (click)="file.click()">
<mat-icon>add</mat-icon>
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>
</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>
</div>
</div>
</mat-card>
</div>

View File

@ -7,23 +7,24 @@
<div class="default-content">
<div class="container mat-elevation-z8">
<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-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-group>
</mat-step>
<mat-step label="Docker Virtual Machine">
<mat-step label="Docker Virtual Machine" [completed]="selectedImage || virtualMachineForm.get('filename').value">
<form [formGroup]="virtualMachineForm">
<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="2" (click)="setDiskImage('newImage')">New image</mat-radio-button>
</mat-radio-group>
<mat-select
*ngIf="!newImageSelected"
placeholder="Image list"
[ngModelOptions]="{standalone: true}"
<mat-select
*ngIf="!newImageSelected"
placeholder="Image list"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="selectedImage">
<mat-option *ngFor="let image of dockerImages" [value]="image">
{{image.image}}
@ -41,7 +42,8 @@
</div>
</form>
</mat-step>
<mat-step label="Container name">
<mat-step label="Container name" [completed]="containerNameForm.get('templateName').value">
<form [formGroup]="containerNameForm">
<mat-form-field class="form-field">
<input
@ -53,7 +55,8 @@
</mat-form-field>
</form>
</mat-step>
<mat-step label="Network adapters">
<mat-step label="Network adapters" [completed]="networkAdaptersForm.get('adapters').value">
<form [formGroup]="networkAdaptersForm">
<mat-form-field class="form-field">
<input
@ -65,7 +68,8 @@
</mat-form-field>
</form>
</mat-step>
<mat-step label="Start command">
<mat-step label="Start command" [completed]="dockerTemplate.start_command">
<mat-form-field class="form-field">
<input
matInput
@ -74,16 +78,18 @@
placeholder="Start command"/>
</mat-form-field>
</mat-step>
<mat-step label="Console type">
<mat-select
placeholder="Console type"
[ngModelOptions]="{standalone: true}"
<mat-step label="Console type" [completed]="dockerTemplate.console_type">
<mat-select
placeholder="Console type"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="dockerTemplate.console_type">
<mat-option *ngFor="let type of consoleTypes" [value]="type">
{{type}}
</mat-option>
</mat-select>
</mat-step>
<mat-step label="Environment">
<mat-form-field class="form-field">
<textarea matInput type="text" [(ngModel)]="dockerTemplate.environment"></textarea>

View File

@ -25,6 +25,7 @@ export class AddDockerTemplateComponent implements OnInit {
consoleTypes: string[] = [];
isRemoteComputerChosen: boolean = false;
dockerImages: DockerImage[] = [];
selectedImage: DockerImage;
newImageSelected: boolean = false;
virtualMachineForm: FormGroup;
@ -49,15 +50,15 @@ export class AddDockerTemplateComponent implements OnInit {
this.dockerTemplate = new DockerTemplate();
this.virtualMachineForm = this.formBuilder.group({
filename: new FormControl('', Validators.required)
filename: new FormControl(null, Validators.required)
});
this.containerNameForm = this.formBuilder.group({
templateName: new FormControl('', Validators.required)
templateName: new FormControl(null, Validators.required)
});
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() {
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.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.adapters = this.networkAdaptersForm.get('adapters').value;
this.dockerTemplate.adapters = +this.networkAdaptersForm.get('adapters').value;
this.dockerTemplate.compute_id = this.isGns3VmChosen ? 'vm' : 'local';
this.dockerService.addTemplate(this.server, this.dockerTemplate).subscribe((template: DockerTemplate) => {

View File

@ -7,14 +7,14 @@
<div class="default-content" *ngIf="iosTemplate">
<div class="container mat-elevation-z8">
<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-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-group>
</mat-step>
<mat-step label="IOS image">
<mat-step label="IOS image" [completed]="iosImageForm.get('imageName').value">
<input
type="file"
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
<form [formGroup]="iosImageForm">
<mat-form-field class="form-field">
<mat-select
placeholder="Image"
<mat-select
placeholder="Image"
(selectionChange)="onImageChosen($event)"
formControlName="imageName">
<mat-option *ngFor="let image of iosImages" [value]="image.filename">
@ -37,17 +37,20 @@
</mat-form-field>
</form>
</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">
<mat-form-field class="form-field">
<input
matInput type="text"
<input
matInput type="text"
formControlName="templateName"
placeholder="Name"/>
</mat-form-field>
<mat-form-field class="form-field">
<mat-select
placeholder="Platform"
<mat-select
placeholder="Platform"
(selectionChange)="onPlatformChosen($event)"
formControlName="platform">
<mat-option *ngFor="let platform of platforms" [value]="platform">
@ -56,8 +59,8 @@
</mat-select>
</mat-form-field>
<mat-form-field class="form-field" *ngIf="chassis[iosNameForm.get('platform').value]">
<mat-select
placeholder="Chassis"
<mat-select
placeholder="Chassis"
(selectionChange)="onChassisChosen($event)"
formControlName="chassis">
<mat-option *ngFor="let chassis of chassis[iosNameForm.get('platform').value]" [value]="chassis">
@ -66,17 +69,18 @@
</mat-select>
</mat-form-field>
</form>
<mat-checkbox
*ngIf="platformsWithEtherSwitchRouterOption[iosTemplate.platform]"
<mat-checkbox
*ngIf="platformsWithEtherSwitchRouterOption[iosTemplate.platform]"
[(ngModel)]="isEtherSwitchRouter">
This is an EtherSwitch router
</mat-checkbox>
</mat-step>
<mat-step label="Memory">
<mat-step label="Memory" [completed]="iosMemoryForm.get('memory').value">
<form [formGroup]="iosMemoryForm">
<mat-form-field class="form-field">
<input
matInput type="number"
<input
matInput type="number"
formControlName="memory"
value="defaultRam[iosNameForm.get('platform').value]"
placeholder="Default RAM"/>
@ -87,54 +91,57 @@
</mat-label>
</form>
</mat-step>
<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]">
<mat-select
placeholder="Slot {{index}}"
<mat-select
placeholder="Slot {{index}}"
[(ngModel)]="networkAdaptersForTemplate[index]"
[ngModelOptions]="{standalone: true}"
*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">
{{option}}
</mat-option>
</mat-select>
</mat-select>
</div>
</div>
<div *ngIf="chassis[iosNameForm.get('platform').value]">
</div> -->
<div *ngIf="selectedPlatform">
<div *ngFor="let index of [0,1,2,3,4,5,6,7]">
<mat-select
placeholder="Slot {{index}}"
<mat-select
placeholder="Slot {{index}}"
[(ngModel)]="networkAdaptersForTemplate[index]"
[ngModelOptions]="{standalone: true}"
*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">
{{option}}
</mat-option>
</mat-select>
</mat-select>
</div>
</div>
</mat-step>
<mat-step label="WIC modules">
<div *ngIf="iosNameForm.get('platform').value">
<div *ngFor="let index of [0,1,2,3]">
<mat-select
placeholder="WIC {{index}}"
<mat-select
placeholder="WIC {{index}}"
[(ngModel)]="networkModulesForTemplate[index]"
[ngModelOptions]="{standalone: true}"
*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">
{{option}}
</mat-option>
</mat-select>
</mat-select>
</div>
</div>
</mat-step>
<mat-step label="Idle-PC">
<mat-form-field class="form-field">
<input
matInput type="text"
[(ngModel)]="iosTemplate.idlepc"
<input
matInput type="text"
[(ngModel)]="iosTemplate.idlepc"
placeholder="Idle-PC"/>
</mat-form-field>
</mat-step>

View File

@ -28,6 +28,7 @@ export class AddIosTemplateComponent implements OnInit {
iosImageForm: FormGroup;
iosNameForm: FormGroup;
iosMemoryForm: FormGroup;
selectedPlatform: string;
networkAdaptersForTemplate: string[] = [];
networkModulesForTemplate: string[] = [];
@ -187,8 +188,8 @@ export class AddIosTemplateComponent implements OnInit {
}
}
} else {
if(Object.keys(this.networkAdaptersForPlatform[this.iosTemplate.chassis])){
for(let i=0; i<Object.keys(this.networkAdaptersForPlatform[this.iosTemplate.chassis]).length; i++){
if(this.networkAdaptersForPlatform[this.iosNameForm.get('platform').value]){
for(let i=0; i<Object.keys(this.networkAdaptersForPlatform[this.iosNameForm.get('platform').value]).length; 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];
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['chassis'].setValue('');
this.iosMemoryForm.controls['memory'].setValue(this.defaultRam[name]);
this.selectedPlatform = 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() {

View File

@ -1,7 +1,7 @@
<div class="content">
<div class="default-header">
<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" routerLink="/server/{{server.id}}/preferences/dynamips/templates/addtemplate" mat-raised-button color="primary">Add IOS router template</button>
</div>

View File

@ -7,19 +7,21 @@
<div class="default-content">
<div class="container mat-elevation-z8">
<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-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-group>
</mat-step>
<mat-step label="Name">
<mat-step label="Name" [completed]="templateNameForm.get('templateName').value">
<form [formGroup]="templateNameForm">
<mat-form-field class="form-field">
<input matInput placeholder="Name" type="text" formControlName="templateName"/>
</mat-form-field>
</form>
</mat-step>
<mat-step label="Image">
<form [formGroup]="imageForm">
<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-group>
<mat-form-field class="form-field">
<mat-select
placeholder="Type"
<mat-select
placeholder="Type"
[(ngModel)]="selectedType"
[ngModelOptions]="{standalone: true}">
<mat-option *ngFor="let type of types" [value]="type">
@ -37,8 +39,8 @@
</mat-select>
</mat-form-field>
<mat-form-field class="form-field" *ngIf="!newImageSelected">
<mat-select
placeholder="IOU image"
<mat-select
placeholder="IOU image"
[(ngModel)]="iouTemplate.path"
[ngModelOptions]="{standalone: true}">
<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.templateNameForm = this.formBuilder.group({
templateName: new FormControl('', Validators.required)
templateName: new FormControl(null, Validators.required)
});
this.imageForm = this.formBuilder.group({
@ -133,6 +133,14 @@ export class AddIouTemplateComponent implements OnInit {
this.iouTemplate.name = this.templateNameForm.get("templateName").value;
if (this.newImageSelected) this.iouTemplate.path = this.imageForm.get("imageName").value;
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.goBack();

View File

@ -1,7 +1,7 @@
<div class="content">
<div class="default-header">
<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" routerLink="/server/{{server.id}}/preferences/iou/addtemplate" mat-raised-button color="primary">Add IOU device template</button>
</div>

View File

@ -7,31 +7,29 @@
<div class="default-content">
<div class="container mat-elevation-z8">
<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-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-group>
</mat-step>
<mat-step label="QEMU VM Name">
<mat-step label="QEMU VM Name" [completed]="nameForm.get('templateName').value">
<form [formGroup]="nameForm">
<mat-form-field class="form-field">
<input
matInput type="text"
<input
matInput type="text"
formControlName="templateName"
placeholder="Please choose a descriptive name for your new QEMU virtual machine"
ngDefaultContro/>
</mat-form-field><br/>
<mat-checkbox>
This is a legacy ASA VM
</mat-checkbox>
</form>
</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">
<mat-form-field class="form-field">
<mat-select
<mat-select
placeholder="Qemu binary"
[(ngModel)]="selectedBinary"
[ngModelOptions]="{standalone: true}">
@ -41,19 +39,20 @@
</mat-select>
</mat-form-field><br/>
<mat-form-field class="form-field">
<input
matInput type="number"
placeholder="RAM"
<input
matInput type="number"
placeholder="RAM"
formControlName="ramMemory"
ngDefaultContro/>
ngDefaultControl/>
<span matSuffix>MB</span>
</mat-form-field>
</form>
</mat-step>
<mat-step label="Console type">
<mat-step label="Console type" [completed]="qemuTemplate.console_type">
<mat-form-field class="form-field">
<mat-select
placeholder="Console type"
<mat-select
placeholder="Console type"
[(ngModel)]="qemuTemplate.console_type"
[ngModelOptions]="{standalone: true}" >
<mat-option *ngFor="let type of consoleTypes" [value]="type">
@ -62,16 +61,17 @@
</mat-select>
</mat-form-field>
</mat-step>
<mat-step label="Disk image">
<form [formGroup]="diskForm">
<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="2" (click)="setDiskImage('newImage')">New image</mat-radio-button>
</mat-radio-group><br/><br/>
<mat-select
*ngIf="!newImageSelected"
placeholder="Disk image (hda)"
[ngModelOptions]="{standalone: true}"
<mat-select
*ngIf="!newImageSelected"
placeholder="Disk image (hda)"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="selectedImage">
<mat-option *ngFor="let image of qemuImages" [value]="image">
{{image.filename}}

View File

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

View File

@ -7,7 +7,7 @@
<div class="default-content">
<mat-card class="matCard">
<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-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()">
<mat-icon>edit</mat-icon>
<mat-icon>find_replace</mat-icon>
<span>Change symbol</span>
</button>

View File

@ -40,6 +40,11 @@
[server]="server"
[node]="nodes[0]"
></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
*ngIf="!projectService.isReadOnly(project) && nodes.length===1"
[server]="server"

View File

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

View File

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

View File

@ -33,7 +33,7 @@
Start all nodes when this project is opened
</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
</mat-checkbox>

View File

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

View File

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

View File

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

View File

@ -40,24 +40,28 @@ export class ServersComponent implements OnInit, OnDestroy {
this.isElectronApp = this.electronService.isElectronApp;
const runningServersNames = this.serverManagement.getRunningServers();
this.serverService.findAll().then((servers: Server[]) => {
servers.forEach((server) => {
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);
this.serverService.serviceInitialized.subscribe(async (value: boolean) => {
if (value) {
this.serverService.findAll().then((servers: Server[]) => {
servers.forEach((server) => {
const serverIndex = runningServersNames.findIndex((serverName) => server.name === serverName);
if(serverIndex >= 0) {
server.status = 'running';
}
},
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);

View File

@ -62,7 +62,10 @@ export class IosConfigurationService {
"c1700": 128,
"c2600": 128,
"c2691": 256,
"c3600": 256,
"c3600": 192,
"c3620": 192,
"c3640": 192,
"c3660": 192,
"c3725": 256,
"c3745": 256,
"c7200": 512
@ -216,6 +219,33 @@ export class IosConfigurationService {
}
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 {
"c2691": {
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 { Server } from '../models/server';
import { Observable } from 'rxjs';
import { HttpServer } from './http-server.service';
import { ToasterService } from './toaster.service';
@Injectable()
export class ServerService {
private tablename = 'servers';
private ready: Promise<any>;
private isIncognitoMode: boolean = false;
private serverIdsInIncognitoMode: string[] = [];
public serviceInitialized: EventEmitter<boolean> = new EventEmitter<boolean>();
constructor(
private indexedDbService: IndexedDbService,
private httpServer: HttpServer
private httpServer: HttpServer,
private toasterService: ToasterService
) {
this.ready = indexedDbService.get().openDatabase(1, evt => {
evt.currentTarget.result.createObjectStore(this.tablename, { keyPath: 'id', autoIncrement: true });
this.indexedDbService.get().openDatabase(1).then(() => {
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> {
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>;
}
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(() => {
const promise = new Promise((resolve, reject) => {
this.indexedDbService
@ -38,6 +68,16 @@ export class ServerService {
}
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(() => {
const promise = new Promise((resolve, reject) => {
this.indexedDbService
@ -52,10 +92,32 @@ export class ServerService {
}
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[]>;
}
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));
}

View File

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