Merge pull request #1368 from GNS3/enhancement/1360

Enhancement/1360
This commit is contained in:
Jeremy Grossmann 2022-07-30 15:50:09 +02:00 committed by GitHub
commit a2338d905b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 125 additions and 76 deletions

View File

@ -17,7 +17,7 @@
"start": "ng serve", "start": "ng serve",
"startforelectron": "ng serve --configuration=electronDev", "startforelectron": "ng serve --configuration=electronDev",
"build": "ng build", "build": "ng build",
"buildforproduction": "ng build --source-map=false --build-optimizer --configuration=production --base-href /static/web-ui/", "buildforproduction": "ng build --source-map=false --configuration=production --base-href /static/web-ui/",
"buildforelectron": "ng build --configuration=electronProd", "buildforelectron": "ng build --configuration=electronProd",
"buildforgithub": "ng build --configuration=githubProd", "buildforgithub": "ng build --configuration=githubProd",
"test": "ng test", "test": "ng test",

View File

@ -71,3 +71,6 @@
.selection-group { .selection-group {
padding-bottom: 20px; padding-bottom: 20px;
} }
.non-visible {
display: none;
}

View File

@ -83,25 +83,18 @@
<!-- GNS3 menu --> <!-- GNS3 menu -->
<mat-menu #mainMenu="matMenu" [overlapTrigger]="false"> <mat-menu #mainMenu="matMenu" [overlapTrigger]="false">
<button mat-menu-item [routerLink]="['/controller', controller.id, 'projects']">
<mat-icon>work</mat-icon>
<span>Go to projects</span>
</button>
<button mat-menu-item [routerLink]="['/controllers']"> <button mat-menu-item [routerLink]="['/controllers']">
<mat-icon>developer_board</mat-icon> <mat-icon>developer_board</mat-icon>
<span>Go to controllers</span> <span>Controllers</span>
</button> </button>
<button mat-menu-item [routerLink]="['/controller', controller.id, 'projects']">
<mat-icon>work</mat-icon>
<span>Projects</span>
</button>
<button mat-menu-item routerLink="/controller/{{ controller.id }}/preferences"> <button mat-menu-item routerLink="/controller/{{ controller.id }}/preferences">
<mat-icon>settings_applications</mat-icon> <mat-icon>settings_applications</mat-icon>
<span>Go to preferences</span> <span>Template preferences</span>
</button>
<button mat-menu-item routerLink="/controller/{{ controller.id }}/systemstatus">
<mat-icon>info</mat-icon>
<span>Go to system status</span>
</button>
<button mat-menu-item routerLink="/settings">
<mat-icon>settings</mat-icon>
<span>Go to settings</span>
</button> </button>
<button mat-menu-item [routerLink]="['/controller', controller.id, 'image-manager']"> <button mat-menu-item [routerLink]="['/controller', controller.id, 'image-manager']">
<mat-icon>collections</mat-icon> <mat-icon>collections</mat-icon>

View File

@ -1,67 +1,53 @@
<header> <header>
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<button mat-icon-button><mat-icon svgIcon="gns3"></mat-icon></button> <button mat-icon-button *ngIf="!isLoginPage && router.url == '/controllers'">
<mat-icon svgIcon="gns3"></mat-icon>
</button>
<button *ngIf="!isLoginPage && router.url != '/controllers'" mat-icon-button matTooltip="Open menu" matTooltipClass="custom-tooltip" [matMenuTriggerFor]="mainMenu">
<mat-icon svgIcon="gns3"></mat-icon>
</button>
<button mat-button routerLink="/controllers">Controllers</button>
<button *ngIf="!recentlyOpenedProjectId && controllerIdProjectList" mat-button (click)="listProjects()"> <button *ngIf="!recentlyOpenedProjectId && controllerIdProjectList" mat-button (click)="listProjects()">
Projects Projects
</button> </button>
<button *ngIf="recentlyOpenedProjectId && recentlyOpenedcontrollerId && !isLoginPage" mat-button (click)="backToProject()"> <button
*ngIf="recentlyOpenedProjectId && recentlyOpenedcontrollerId && !isLoginPage"
mat-button
(click)="backToProject()"
>
Back to project Back to project
</button> </button>
<span class="fill-space"></span> <span class="fill-space"></span>
<button mat-button *ngIf="!isLoginPage && router.url !='/controllers'" [matMenuTriggerFor]="menu"> <button mat-button *ngIf="!isLoginPage && router.url != '/controllers'" [matMenuTriggerFor]="menu">
<mat-icon>more_vert</mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
<!-- GNS3 right menu -->
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">
<button mat-menu-item <button mat-menu-item [disabled]="!controllerId" [routerLink]="['controller', controllerId, 'systemstatus']">
[disabled]="!controllerId"
[routerLink]="['controller', controllerId, 'systemstatus']">
<mat-icon>info</mat-icon> <mat-icon>info</mat-icon>
<span>System status</span> <span>System status</span>
</button> </button>
<button mat-menu-item <button mat-menu-item [disabled]="!controllerId" [routerLink]="['controller', controllerId, 'settings']">
[disabled]="!controllerId"
[routerLink]="['controller', controllerId, 'preferences']">
<mat-icon>settings_applications</mat-icon>
<span>Template preferences</span>
</button>
<button mat-menu-item
[disabled]="!controllerId"
[routerLink]="['controller', controllerId, 'image-manager']">
<mat-icon>collections</mat-icon>
<span>Image manager</span>
</button>
<button mat-menu-item
[disabled]="!controllerId"
[routerLink]="['controller', controllerId, 'settings']"
>
<mat-icon>settings</mat-icon> <mat-icon>settings</mat-icon>
<span>Settings</span> <span>Settings</span>
</button> </button>
<button mat-menu-item <button
[disabled]="!controllerId" mat-menu-item
[routerLink]="['controller', controllerId, 'management', 'users']"> [disabled]="!controllerId"
[routerLink]="['controller', controllerId, 'management', 'users']"
>
<mat-icon>groups</mat-icon> <mat-icon>groups</mat-icon>
<span>Management</span> <span>Management</span>
</button> </button>
<button mat-menu-item <button mat-menu-item [disabled]="!controllerId" [routerLink]="['controller', controllerId, 'help']">
[disabled]="!controllerId"
[routerLink]="['controller', controllerId, 'help']"
>
<mat-icon>help</mat-icon> <mat-icon>help</mat-icon>
<span>Help</span> <span>Help</span>
</button> </button>
<button <button [disabled]="!controllerId" [routerLink]="['/controller', controllerId, 'loggeduser']" mat-menu-item>
[disabled]="!controllerId"
[routerLink]="['/controller', controllerId, 'loggeduser']"
mat-menu-item>
<mat-icon>person</mat-icon> <mat-icon>person</mat-icon>
<span>User info</span> <span>User info</span>
</button> </button>
@ -74,6 +60,29 @@
<span>Logout</span> <span>Logout</span>
</button> </button>
</mat-menu> </mat-menu>
<!-- GNS3 left menu -->
<mat-menu #mainMenu="matMenu" [overlapTrigger]="false">
<button mat-menu-item [routerLink]="['/controllers']">
<mat-icon>developer_board</mat-icon>
<span>Controllers</span>
</button>
<button mat-menu-item [routerLink]="['/controller', controllerId, 'projects']">
<mat-icon>work</mat-icon>
<span>Projects</span>
</button>
<button mat-menu-item [disabled]="!controllerId" [routerLink]="['controller', controllerId, 'preferences']">
<mat-icon>settings_applications</mat-icon>
<span>Template preferences</span>
</button>
<button mat-menu-item [disabled]="!controllerId" [routerLink]="['controller', controllerId, 'image-manager']">
<mat-icon>collections</mat-icon>
<span>Image manager</span>
</button>
<button mat-menu-item (click)="addNewTemplate()">
<mat-icon>control_point</mat-icon>
<span>New template</span>
</button>
</mat-menu>
</mat-toolbar> </mat-toolbar>
</header> </header>

View File

@ -1,21 +1,24 @@
import { HttpClientModule } from '@angular/common/http';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { ControllerService } from '../../services/controller.service'; import { ProjectService } from '../../services/project.service';
import { MockedProjectService } from '../../services/project.service.spec';
import { ElectronService } from 'ngx-electron'; import { ElectronService } from 'ngx-electron';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { ProgressComponent } from '../../common/progress/progress.component'; import { ProgressComponent } from '../../common/progress/progress.component';
import { ProgressService } from '../../common/progress/progress.service'; import { ProgressService } from '../../common/progress/progress.service';
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
import { ControllerManagementService, ControllerStateEvent } from '../../services/controller-management.service'; import { ControllerManagementService, ControllerStateEvent } from '../../services/controller-management.service';
import { ControllerService } from '../../services/controller.service';
import { ControllerErrorHandler, HttpController } from '../../services/http-controller.service';
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
import { ToasterService } from '../../services/toaster.service'; import { ToasterService } from '../../services/toaster.service';
import { MockedToasterService } from '../../services/toaster.service.spec'; import { MockedToasterService } from '../../services/toaster.service.spec';
import { DefaultLayoutComponent } from './default-layout.component'; import { DefaultLayoutComponent } from './default-layout.component';
import { HttpController, ControllerErrorHandler } from '../../services/http-controller.service';
import { HttpClientModule } from '@angular/common/http';
class ElectronServiceMock { class ElectronServiceMock {
public isElectronApp: boolean; public isElectronApp: boolean;
@ -35,13 +38,21 @@ describe('DefaultLayoutComponent', () => {
let httpController: HttpController; let httpController: HttpController;
let errorHandler: ControllerErrorHandler; let errorHandler: ControllerErrorHandler;
beforeEach(async() => { beforeEach(async () => {
electronServiceMock = new ElectronServiceMock(); electronServiceMock = new ElectronServiceMock();
controllerManagementService.controllerStatusChanged = new Subject<ControllerStateEvent>(); controllerManagementService.controllerStatusChanged = new Subject<ControllerStateEvent>();
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [DefaultLayoutComponent, ProgressComponent], declarations: [DefaultLayoutComponent, ProgressComponent],
imports: [MatIconModule, MatMenuModule, MatToolbarModule, HttpClientModule, RouterTestingModule, MatProgressSpinnerModule], imports: [
MatIconModule,
MatMenuModule,
MatDialogModule,
MatToolbarModule,
HttpClientModule,
RouterTestingModule,
MatProgressSpinnerModule,
],
providers: [ providers: [
{ {
provide: ElectronService, provide: ElectronService,
@ -51,6 +62,8 @@ describe('DefaultLayoutComponent', () => {
provide: ControllerManagementService, provide: ControllerManagementService,
useValue: controllerManagementService, useValue: controllerManagementService,
}, },
{ provide: ProjectService, useClass: MockedProjectService },
{ {
provide: ToasterService, provide: ToasterService,
useClass: MockedToasterService, useClass: MockedToasterService,
@ -62,6 +75,8 @@ describe('DefaultLayoutComponent', () => {
{ provide: ControllerService }, { provide: ControllerService },
{ provide: HttpController }, { provide: HttpController },
{ provide: ControllerErrorHandler }, { provide: ControllerErrorHandler },
{ provide: MatDialogRef, useValue: {}},
{ provide: MAT_DIALOG_DATA, useValue: {}},
ProgressService, ProgressService,
], ],
}).compileComponents(); }).compileComponents();

View File

@ -1,15 +1,18 @@
import { Component, HostListener, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, HostListener, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { NavigationEnd } from '@angular/router'; import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { ControllerService } from '../../services/controller.service'; import { ProjectService } from '@services/project.service';
import { ElectronService } from 'ngx-electron'; import { ElectronService } from 'ngx-electron';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ProgressService } from '../../common/progress/progress.service'; import { ProgressService } from '../../common/progress/progress.service';
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service'; import { NewTemplateDialogComponent } from '../../components/project-map/new-template-dialog/new-template-dialog.component';
import { Controller } from '../../models/controller';
import { Project } from '../../models/project';
import { ControllerManagementService } from '../../services/controller-management.service'; import { ControllerManagementService } from '../../services/controller-management.service';
import { ControllerService } from '../../services/controller.service';
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
import { ToasterService } from '../../services/toaster.service'; import { ToasterService } from '../../services/toaster.service';
import { version } from './../../version'; import { version } from './../../version';
import { Controller } from '../../models/controller';
@Component({ @Component({
selector: 'app-default-layout', selector: 'app-default-layout',
@ -29,6 +32,9 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
recentlyOpenedProjectId: string; recentlyOpenedProjectId: string;
controllerIdProjectList: string; controllerIdProjectList: string;
controllerId: string | undefined | null; controllerId: string | undefined | null;
public controller: Controller;
public project: Project;
private projectMapSubscription: Subscription = new Subscription();
constructor( constructor(
private electronService: ElectronService, private electronService: ElectronService,
@ -36,19 +42,21 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
private controllerManagement: ControllerManagementService, private controllerManagement: ControllerManagementService,
private toasterService: ToasterService, private toasterService: ToasterService,
private progressService: ProgressService, private progressService: ProgressService,
private dialog: MatDialog,
public router: Router, public router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private controllerService: ControllerService private controllerService: ControllerService,
private projectService: ProjectService
) { ) {
this.router.events.subscribe((data) => { this.router.events.subscribe((data) => {
if (data instanceof NavigationEnd) { if (data instanceof NavigationEnd) {
this.controllerId = this.route.children[0].snapshot.paramMap.get("controller_id"); this.controllerId = this.route.children[0].snapshot.paramMap.get('controller_id');
this.getData();
} }
}); });
} }
ngOnInit() { ngOnInit() {
this.checkIfUserIsLoginPage(); this.checkIfUserIsLoginPage();
this.routeSubscription = this.router.events.subscribe((val) => { this.routeSubscription = this.router.events.subscribe((val) => {
if (val instanceof NavigationEnd) this.checkIfUserIsLoginPage(); if (val instanceof NavigationEnd) this.checkIfUserIsLoginPage();
@ -61,16 +69,18 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
this.isInstalledSoftwareAvailable = this.electronService.isElectronApp; this.isInstalledSoftwareAvailable = this.electronService.isElectronApp;
// attach to notification stream when any of running local controllers experienced issues // attach to notification stream when any of running local controllers experienced issues
this.controllerStatusSubscription = this.controllerManagement.controllerStatusChanged.subscribe((controllerStatus) => { this.controllerStatusSubscription = this.controllerManagement.controllerStatusChanged.subscribe(
if (controllerStatus.status === 'errored') { (controllerStatus) => {
console.error(controllerStatus.message); if (controllerStatus.status === 'errored') {
this.toasterService.error(controllerStatus.message); console.error(controllerStatus.message);
this.toasterService.error(controllerStatus.message);
}
if (controllerStatus.status === 'stderr') {
console.error(controllerStatus.message);
this.toasterService.error(controllerStatus.message);
}
} }
if (controllerStatus.status === 'stderr') { );
console.error(controllerStatus.message);
this.toasterService.error(controllerStatus.message);
}
});
// stop controllers only when in Electron // stop controllers only when in Electron
this.shouldStopControllersOnClosing = this.electronService.isElectronApp; this.shouldStopControllersOnClosing = this.electronService.isElectronApp;
@ -83,7 +93,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
} }
checkIfUserIsLoginPage() { checkIfUserIsLoginPage() {
if (this.router.url.includes("login")) { if (this.router.url.includes('login')) {
this.isLoginPage = true; this.isLoginPage = true;
} else { } else {
this.isLoginPage = false; this.isLoginPage = false;
@ -93,7 +103,9 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
logout() { logout() {
this.controllerService.get(+this.controllerId).then((controller: Controller) => { this.controllerService.get(+this.controllerId).then((controller: Controller) => {
controller.authToken = null; controller.authToken = null;
this.controllerService.update(controller).then(val => this.router.navigate(['/controller', controller.id, 'login'])); this.controllerService
.update(controller)
.then((val) => this.router.navigate(['/controller', controller.id, 'login']));
}); });
} }
@ -123,6 +135,23 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
window.close(); window.close();
return false; return false;
} }
getData() {
this.controllerService.get(+this.controllerId).then((controller: Controller) => {
this.controller = controller;
});
}
public addNewTemplate() {
const dialogRef = this.dialog.open(NewTemplateDialogComponent, {
width: '1000px',
maxHeight: '700px',
autoFocus: false,
disableClose: true,
});
let instance = dialogRef.componentInstance;
instance.controller = this.controller;
instance.project = this.project;
}
ngOnDestroy() { ngOnDestroy() {
this.controllerStatusSubscription.unsubscribe(); this.controllerStatusSubscription.unsubscribe();