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

This commit is contained in:
piotrpekala7 2020-06-17 17:40:01 +02:00
commit 561226f31b
15 changed files with 203 additions and 150 deletions

View File

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

View File

@ -1,6 +1,25 @@
GNS3 WebUI is web implementation of user interface for GNS3 software.
Current version: GNS3 Web UI 2020.2.0-beta.4
Current version: 2020.2.0-beta.5
Bug Fixes
- Bug in symbol selection
- Same question is asked after going back to project
- Cannot read property 'forEach' of undefined
- Error when selecting existing Docker image
- Invalid property when adding VMware VM template
- Invalid type for adapters field when adding Docker template
- Prevent user to move to another step when adding template
- Web UI cannot set flag "Leave this project running in the background after closing"
What's new
- Default values in templates
- New option for Qemu VMs
- Ability to quickly change Hostname from right click
- Progress bar for node creation
GNS3 Web UI 2020.2.0-beta.4
Bug Fixes
- New port setting for GNS3 VM preferences

View File

@ -62,7 +62,6 @@ import { Gns3vmComponent } from './components/preferences/gns3vm/gns3vm.componen
import { DirectLinkComponent } from './components/direct-link/direct-link.component';
import { SystemStatusComponent } from './components/system-status/system-status.component';
import { ServerResolve } from './resolvers/server-resolve';
import { ProjectMapGuard } from './guards/project-map-guard';
import { WebConsoleFullWindowComponent } from './components/web-console-full-window/web-console-full-window.component';
import { ConsoleGuard } from './guards/console-guard';
@ -154,13 +153,11 @@ const routes: Routes = [
{
path: 'server/:server_id/project/:project_id',
component: ProjectMapComponent,
canActivate: [ProjectMapGuard],
canDeactivate: [ConsoleGuard]
},
{
path: 'server/:server_id/project/:project_id/nodes/:node_id',
component: WebConsoleFullWindowComponent,
canActivate: [ProjectMapGuard]
component: WebConsoleFullWindowComponent
},
{
path: '**',

View File

@ -267,7 +267,6 @@ import { NgCircleProgressModule } from 'ng-circle-progress';
import { OpenFileExplorerActionComponent } from './components/project-map/context-menu/actions/open-file-explorer/open-file-explorer-action.component';
import { NgxChildProcessModule } from 'ngx-childprocess';
import { ServerResolve } from './resolvers/server-resolve';
import { ProjectMapGuard } from './guards/project-map-guard';
import { HttpConsoleActionComponent } from './components/project-map/context-menu/actions/http-console/http-console-action.component';
import { WebConsoleComponent } from './components/project-map/web-console/web-console.component';
import { ConsoleWrapperComponent } from './components/project-map/console-wrapper/console-wrapper.component';
@ -566,7 +565,6 @@ if (environment.production) {
GoogleAnalyticsService,
NodeConsoleService,
ServerResolve,
ProjectMapGuard,
ConsoleGuard,
Title,
ApplianceService

View File

@ -1,13 +1,13 @@
<svg id="map" #svg class="map" preserveAspectRatio="none" movingCanvas zoomingCanvas>
<filter id="grayscale"><feColorMatrix id="feGrayscale" type="saturate" values="0" /></filter>
<defs>
<pattern id="gridDrawing" attr.width="{{project.drawing_grid_size}}" attr.height="{{project.drawing_grid_size}}" patternUnits="userSpaceOnUse">
<pattern attr.x="{{drawingGridX}}" attr.y="{{drawingGridY}}" id="gridDrawing" attr.width="{{project.drawing_grid_size}}" attr.height="{{project.drawing_grid_size}}" patternUnits="userSpaceOnUse">
<path attr.d="M {{project.drawing_grid_size}} 0 L 0 0 0 {{project.drawing_grid_size}}" fill="none" stroke="silver" attr.stroke-width="{{gridVisibility}}"/>
</pattern>
</defs>
<defs>
<pattern id="gridNode" attr.width="{{project.grid_size}}" attr.height="{{project.grid_size}}" patternUnits="userSpaceOnUse">
<pattern attr.x="{{nodeGridX}}" attr.y="{{nodeGridY}}" id="gridNode" attr.width="{{project.grid_size}}" attr.height="{{project.grid_size}}" patternUnits="userSpaceOnUse">
<path attr.d="M {{project.grid_size}} 0 L 0 0 0 {{project.grid_size}}" fill="none" stroke="DarkSlateGray" attr.stroke-width="{{gridVisibility}}"/>
</pattern>
</defs>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -64,6 +64,11 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
};
public gridVisibility: number = 0;
public nodeGridX: number = 0;
public nodeGridY: number = 0;
public drawingGridX: number = 0;
public drawingGridY: number = 0;
constructor(
private graphDataManager: GraphDataManager,
public context: Context,
@ -189,6 +194,8 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
}
private redraw() {
this.updateGrid();
this.graphDataManager.setNodes(this.nodes);
this.graphDataManager.setLinks(this.links);
this.graphDataManager.setDrawings(this.drawings);
@ -198,6 +205,14 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
this.mapSettingsService.mapRenderedEmitter.emit(true);
}
updateGrid() {
this.nodeGridX = (this.project.scene_width/2 - (Math.floor(this.project.scene_width/2 / this.project.grid_size) * this.project.grid_size));
this.nodeGridY = (this.project.scene_height/2 - (Math.floor(this.project.scene_height/2 / this.project.grid_size) * this.project.grid_size));
this.drawingGridX = (this.project.scene_width/2 - (Math.floor(this.project.scene_width/2 / this.project.drawing_grid_size) * this.project.drawing_grid_size));
this.drawingGridY = (this.project.scene_height/2 - (Math.floor(this.project.scene_height/2 / this.project.drawing_grid_size) * this.project.drawing_grid_size));
}
@HostListener('window:resize', ['$event'])
onResize(event) {
this.changeLayout();

View File

@ -21,29 +21,35 @@ export class DirectLinkComponent implements OnInit {
) {}
async ngOnInit() {
if (this.serverService.isServiceInitialized) this.getServers();
this.serverService.serviceInitialized.subscribe(async (value: boolean) => {
if (value) {
this.getServers();
}
});
}
private async getServers() {
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];
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]);
});
}
}
});
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]);
});
}
}
}

View File

@ -170,56 +170,16 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.mapSettingsService.logConsoleSubject.subscribe(value => this.isConsoleVisible = value);
this.progressService.activate();
const routeSub = this.route.paramMap.subscribe((paramMap: ParamMap) => {
const server_id = parseInt(paramMap.get('server_id'), 10);
from(this.serverService.get(server_id))
.pipe(
mergeMap((server: Server) => {
this.server = server;
return this.projectService.get(server, paramMap.get('project_id')).pipe(
map(project => {
return project;
})
);
}),
mergeMap((project: Project) => {
this.project = project;
this.projectService.open(this.server, this.project.project_id);
this.title.setTitle(this.project.name);
if (this.mapSettingsService.interfaceLabels.has(project.project_id)) {
this.isInterfaceLabelVisible = this.mapSettingsService.interfaceLabels.get(project.project_id);
} else {
this.isInterfaceLabelVisible = this.project.show_interface_labels;
}
this.recentlyOpenedProjectService.setServerId(this.server.id.toString());
this.recentlyOpenedProjectService.setProjectId(this.project.project_id);
if (this.project.status === 'opened') {
return new Observable<Project>(observer => {
observer.next(this.project);
});
} else {
return this.projectService.open(this.server, this.project.project_id);
}
})
)
.subscribe(
(project: Project) => {
this.onProjectLoad(project);
},
error => {
this.progressService.setError(error);
},
() => {
this.progressService.deactivate();
}
);
});
this.projectMapSubscription.add(routeSub);
if (this.serverService.isServiceInitialized) {
this.getData();
} else {
this.projectMapSubscription.add(
this.serverService.serviceInitialized.subscribe((val) => {
if (val) this.getData();
})
);
}
this.projectMapSubscription.add(
this.mapSettingsService.mapRenderedEmitter.subscribe((value: boolean) => {
@ -273,6 +233,63 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.addKeyboardListeners();
}
getData() {
const routeSub = this.route.paramMap.subscribe((paramMap: ParamMap) => {
const server_id = parseInt(paramMap.get('server_id'), 10);
from(this.serverService.get(server_id))
.pipe(
mergeMap((server: Server) => {
if (!server ) this.router.navigate(['/servers']);
this.server = server;
return this.projectService.get(server, paramMap.get('project_id')).pipe(
map(project => {
return project;
})
);
}),
mergeMap((project: Project) => {
this.project = project;
if (!project ) this.router.navigate(['/servers']);
this.projectService.open(this.server, this.project.project_id);
this.title.setTitle(this.project.name);
if (this.mapSettingsService.interfaceLabels.has(project.project_id)) {
this.isInterfaceLabelVisible = this.mapSettingsService.interfaceLabels.get(project.project_id);
} else {
this.isInterfaceLabelVisible = this.project.show_interface_labels;
}
this.recentlyOpenedProjectService.setServerId(this.server.id.toString());
this.recentlyOpenedProjectService.setProjectId(this.project.project_id);
if (this.project.status === 'opened') {
return new Observable<Project>(observer => {
observer.next(this.project);
});
} else {
return this.projectService.open(this.server, this.project.project_id);
}
})
)
.subscribe(
(project: Project) => {
this.onProjectLoad(project);
},
error => {
this.progressService.setError(error);
},
() => {
this.progressService.deactivate();
}
);
});
this.projectMapSubscription.add(routeSub);
}
addKeyboardListeners() {
Mousetrap.bind('ctrl++', (event: Event) => {
event.preventDefault();

View File

@ -53,6 +53,8 @@ export class ProjectsComponent implements OnInit {
ngOnInit() {
this.server = this.route.snapshot.data['server'];
if(!this.server) this.router.navigate(['/servers']);
this.refresh();
this.sort.sort(<MatSortable>{
id: 'name',

View File

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

View File

@ -35,32 +35,39 @@ export class ServersComponent implements OnInit, OnDestroy {
private childProcessService: ChildProcessService,
private bottomSheet: MatBottomSheet,
) {}
getServers() {
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);
}
},
error => {}
);
});
});
}
ngOnInit() {
this.isElectronApp = this.electronService.isElectronApp;
const runningServersNames = this.serverManagement.getRunningServers();
if (this.serverService.isServiceInitialized) this.getServers();
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';
}
});
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.getServers();
}
});

View File

@ -10,6 +10,7 @@ import { ActivatedRoute } from '@angular/router';
import { ServerService } from '../../services/server.service';
import { Title } from '@angular/platform-browser';
import { NodeService } from '../../services/node.service';
import { Subscription } from 'rxjs';
@Component({
@ -22,7 +23,7 @@ export class WebConsoleFullWindowComponent implements OnInit {
private serverId: string;
private projectId: string;
private nodeId: string;
private subscriptions: Subscription = new Subscription();
private server: Server;
private node: Node;
@ -40,6 +41,18 @@ export class WebConsoleFullWindowComponent implements OnInit {
) {}
ngOnInit() {
if (this.serverService.isServiceInitialized) {
this.getData();
} else {
this.subscriptions.add(
this.serverService.serviceInitialized.subscribe((val) => {
if (val) this.getData();
})
);
}
}
getData() {
this.serverId = this.route.snapshot.paramMap.get("server_id");
this.projectId = this.route.snapshot.paramMap.get("project_id");
this.nodeId = this.route.snapshot.paramMap.get("node_id");
@ -55,7 +68,7 @@ export class WebConsoleFullWindowComponent implements OnInit {
this.title.setTitle(this.node.name);
this.openTerminal();
});
})
});
}
openTerminal() {

View File

@ -1,39 +0,0 @@
import { Injectable } from "@angular/core";
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { ProjectMapComponent } from '../components/project-map/project-map.component';
import { Observable, pipe, timer, from } from 'rxjs';
import { ProjectService } from '../services/project.service';
import { Server } from '../models/server';
import { ServerService } from '../services/server.service';
import { switchMap, map } from 'rxjs/operators';
import { ToasterService } from '../services/toaster.service';
@Injectable()
export class ProjectMapGuard implements CanActivate {
constructor(
private projectService: ProjectService,
private serverService: ServerService,
private toasterService: ToasterService,
private router: Router
) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const server_id = route.paramMap.get("server_id");
const project_id = route.paramMap.get("project_id");
return from(this.serverService.get(parseInt(server_id, 10))).pipe(
switchMap(response => {
if (!response) this.router.navigate(['/servers']);
return this.projectService.list(response as Server)
}),
map(response => {
let projectToOpen = response.find(n => n.project_id === project_id);
if (projectToOpen) return true;
this.toasterService.error('Project could not be opened');
this.projectService.projectListUpdated();
return false;
})
)
}
}

View File

@ -70,7 +70,7 @@ export class NodeService {
if (project.snap_to_grid) {
xPosition = Math.round((xPosition + node.width/2) / project.grid_size) * project.grid_size;
yPosition = Math.round((yPosition + node.width/2) / project.grid_size) * project.grid_size;
yPosition = Math.round((yPosition + node.height/2) / project.grid_size) * project.grid_size;
xPosition = Math.round(xPosition - node.width/2);
yPosition = Math.round(yPosition - node.height/2);

View File

@ -1,9 +1,8 @@
import { Injectable, EventEmitter } from '@angular/core';
import { IndexedDbService } from './indexed-db.service';
import { Server } from '../models/server';
import { Observable } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { HttpServer } from './http-server.service';
import { ToasterService } from './toaster.service';
@Injectable()
export class ServerService {
@ -11,24 +10,41 @@ export class ServerService {
private ready: Promise<any>;
private isIncognitoMode: boolean = false;
private serverIdsInIncognitoMode: string[] = [];
public serviceInitialized: EventEmitter<boolean> = new EventEmitter<boolean>();
public serviceInitialized: Subject<boolean> = new Subject<boolean>();
public isServiceInitialized: boolean;
constructor(
private indexedDbService: IndexedDbService,
private httpServer: HttpServer,
private toasterService: ToasterService
private httpServer: HttpServer
) {
this.indexedDbService.get().openDatabase(1).then(() => {
this.ready = indexedDbService.get().openDatabase(1, evt => {
evt.currentTarget.result.createObjectStore(this.tablename, { keyPath: 'id', autoIncrement: true });
this.ready = this.indexedDbService.get().openDatabase(1, evt => {
evt.currentTarget.result.createObjectStore(this.tablename, { keyPath: 'id', autoIncrement: true });
}).then(() => {
this.indexedDbService.get().getAll(this.tablename)
.then(() => {})
.catch(() => {
this.isIncognitoMode = true;
});
}).catch(() => {
this.isIncognitoMode = true;
}).finally(() => {
this.serviceInitialized.emit(true);
this.isServiceInitialized = true;
this.serviceInitialized.next(true);
});
}
public tryToCreateDb() {
let promise = new Promise(resolve => {
this.indexedDbService.get().openDatabase(1, evt => {
evt.currentTarget.result.createObjectStore(this.tablename, { keyPath: 'id', autoIncrement: true });
}).then(() => {
}).catch(() => {
this.isIncognitoMode = true;
});
});
return promise;
}
public get(id: number): Promise<Server> {
if (this.isIncognitoMode) {
let server: Server = JSON.parse(localStorage.getItem(`server-${id}`));