diff --git a/package.json b/package.json index 050d6dbd..f3cb140c 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/ReleaseNotes.txt b/src/ReleaseNotes.txt index 7ed9c210..9bf889c5 100644 --- a/src/ReleaseNotes.txt +++ b/src/ReleaseNotes.txt @@ -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 diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 5b9ded88..7e9b49fe 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -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: '**', diff --git a/src/app/app.module.ts b/src/app/app.module.ts index eee83a7a..c99e98e2 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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 diff --git a/src/app/cartography/components/d3-map/d3-map.component.html b/src/app/cartography/components/d3-map/d3-map.component.html index f1b00cfe..b409cd50 100644 --- a/src/app/cartography/components/d3-map/d3-map.component.html +++ b/src/app/cartography/components/d3-map/d3-map.component.html @@ -1,13 +1,13 @@ - + - + diff --git a/src/app/cartography/components/d3-map/d3-map.component.ts b/src/app/cartography/components/d3-map/d3-map.component.ts index d0ed257e..6f2a1746 100644 --- a/src/app/cartography/components/d3-map/d3-map.component.ts +++ b/src/app/cartography/components/d3-map/d3-map.component.ts @@ -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(); diff --git a/src/app/components/direct-link/direct-link.component.ts b/src/app/components/direct-link/direct-link.component.ts index d76496bb..b85eac28 100644 --- a/src/app/components/direct-link/direct-link.component.ts +++ b/src/app/components/direct-link/direct-link.component.ts @@ -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]); + }); + } } } diff --git a/src/app/components/project-map/project-map.component.ts b/src/app/components/project-map/project-map.component.ts index e3d4d62c..32abc9f6 100644 --- a/src/app/components/project-map/project-map.component.ts +++ b/src/app/components/project-map/project-map.component.ts @@ -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(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(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(); diff --git a/src/app/components/projects/projects.component.ts b/src/app/components/projects/projects.component.ts index 74799dee..1f17d649 100644 --- a/src/app/components/projects/projects.component.ts +++ b/src/app/components/projects/projects.component.ts @@ -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({ id: 'name', diff --git a/src/app/components/servers/server-discovery/server-discovery.component.ts b/src/app/components/servers/server-discovery/server-discovery.component.ts index 3dec2dd6..e61e26b8 100644 --- a/src/app/components/servers/server-discovery/server-discovery.component.ts +++ b/src/app/components/servers/server-discovery/server-discovery.component.ts @@ -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(); diff --git a/src/app/components/servers/servers.component.ts b/src/app/components/servers/servers.component.ts index de6fc3db..0ee25f50 100644 --- a/src/app/components/servers/servers.component.ts +++ b/src/app/components/servers/servers.component.ts @@ -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(); } }); diff --git a/src/app/components/web-console-full-window/web-console-full-window.component.ts b/src/app/components/web-console-full-window/web-console-full-window.component.ts index d7db07c1..0e284d1b 100644 --- a/src/app/components/web-console-full-window/web-console-full-window.component.ts +++ b/src/app/components/web-console-full-window/web-console-full-window.component.ts @@ -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() { diff --git a/src/app/guards/project-map-guard.ts b/src/app/guards/project-map-guard.ts deleted file mode 100644 index 54649f0e..00000000 --- a/src/app/guards/project-map-guard.ts +++ /dev/null @@ -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 { - 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; - }) - ) - } -} diff --git a/src/app/services/node.service.ts b/src/app/services/node.service.ts index 1231408c..0b58fbaa 100644 --- a/src/app/services/node.service.ts +++ b/src/app/services/node.service.ts @@ -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); diff --git a/src/app/services/server.service.ts b/src/app/services/server.service.ts index 5dfedebf..d677c178 100644 --- a/src/app/services/server.service.ts +++ b/src/app/services/server.service.ts @@ -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; private isIncognitoMode: boolean = false; private serverIdsInIncognitoMode: string[] = []; - public serviceInitialized: EventEmitter = new EventEmitter(); + public serviceInitialized: Subject = new Subject(); + 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 { if (this.isIncognitoMode) { let server: Server = JSON.parse(localStorage.getItem(`server-${id}`));