mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-05-09 20:12:53 +00:00
Merge branch 'master' into List-of-debug-events
This commit is contained in:
commit
fc746f04cb
@ -193,6 +193,7 @@ import { ProjectMapMenuComponent } from './components/project-map/project-map-me
|
|||||||
import { HelpComponent } from './components/help/help.component';
|
import { HelpComponent } from './components/help/help.component';
|
||||||
import { LogConsoleComponent } from './components/project-map/log-console/log-console.component';
|
import { LogConsoleComponent } from './components/project-map/log-console/log-console.component';
|
||||||
import { LogEventsDataSource } from './components/project-map/log-console/log-events-datasource';
|
import { LogEventsDataSource } from './components/project-map/log-console/log-events-datasource';
|
||||||
|
import { SaveProjectDialogComponent } from './components/projects/save-project-dialog/save-project-dialog.component';
|
||||||
import { TopologySummaryComponent } from './components/topology-summary/topology-summary.component';
|
import { TopologySummaryComponent } from './components/topology-summary/topology-summary.component';
|
||||||
import { ShowNodeActionComponent } from './components/project-map/context-menu/actions/show-node-action/show-node-action.component';
|
import { ShowNodeActionComponent } from './components/project-map/context-menu/actions/show-node-action/show-node-action.component';
|
||||||
import { InfoDialogComponent } from './components/project-map/info-dialog/info-dialog.component';
|
import { InfoDialogComponent } from './components/project-map/info-dialog/info-dialog.component';
|
||||||
@ -322,6 +323,7 @@ if (environment.production) {
|
|||||||
ProjectMapMenuComponent,
|
ProjectMapMenuComponent,
|
||||||
HelpComponent,
|
HelpComponent,
|
||||||
LogConsoleComponent,
|
LogConsoleComponent,
|
||||||
|
SaveProjectDialogComponent,
|
||||||
TopologySummaryComponent,
|
TopologySummaryComponent,
|
||||||
InfoDialogComponent,
|
InfoDialogComponent,
|
||||||
BringToFrontActionComponent
|
BringToFrontActionComponent
|
||||||
@ -419,6 +421,7 @@ if (environment.production) {
|
|||||||
DeleteConfirmationDialogComponent,
|
DeleteConfirmationDialogComponent,
|
||||||
HelpDialogComponent,
|
HelpDialogComponent,
|
||||||
StartCaptureDialogComponent,
|
StartCaptureDialogComponent,
|
||||||
|
SaveProjectDialogComponent,
|
||||||
InfoDialogComponent
|
InfoDialogComponent
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
@ -40,11 +40,27 @@
|
|||||||
<mat-menu #mainMenu="matMenu" [overlapTrigger]="false">
|
<mat-menu #mainMenu="matMenu" [overlapTrigger]="false">
|
||||||
<button mat-menu-item [routerLink]="['/server', server.id, 'projects']">
|
<button mat-menu-item [routerLink]="['/server', server.id, 'projects']">
|
||||||
<mat-icon>work</mat-icon>
|
<mat-icon>work</mat-icon>
|
||||||
<span>Projects</span>
|
<span>Go to projects</span>
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item [routerLink]="['/servers']">
|
<button mat-menu-item [routerLink]="['/servers']">
|
||||||
<mat-icon>developer_board</mat-icon>
|
<mat-icon>developer_board</mat-icon>
|
||||||
<span>Servers</span>
|
<span>Go to servers</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="addNewProject()">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
<span>Add new blank project</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="saveProject()">
|
||||||
|
<mat-icon>save</mat-icon>
|
||||||
|
<span>Save project as</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="exportProject()">
|
||||||
|
<mat-icon>call_made</mat-icon>
|
||||||
|
<span>Export portable project</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="importProject()">
|
||||||
|
<mat-icon>call_received</mat-icon>
|
||||||
|
<span>Import portable project</span>
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item (click)="deleteProject()">
|
<button mat-menu-item (click)="deleteProject()">
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon>delete</mat-icon>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { ProjectMapComponent } from './project-map.component';
|
import { ProjectMapComponent } from './project-map.component';
|
||||||
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule } from '@angular/material';
|
import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatDialogModule } from '@angular/material';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { ServerService } from '../../services/server.service';
|
import { ServerService } from '../../services/server.service';
|
||||||
import { ProjectService } from '../../services/project.service';
|
import { ProjectService } from '../../services/project.service';
|
||||||
@ -240,7 +240,7 @@ describe('ProjectMapComponent', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
|
imports: [MatIconModule, MatDialogModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute },
|
{ provide: ActivatedRoute },
|
||||||
{ provide: ServerService, useClass: MockedServerService },
|
{ provide: ServerService, useClass: MockedServerService },
|
||||||
|
@ -51,6 +51,10 @@ import { LabelWidget } from '../../cartography/widgets/label';
|
|||||||
import { MapLinkNodeToLinkNodeConverter } from '../../cartography/converters/map/map-link-node-to-link-node-converter';
|
import { MapLinkNodeToLinkNodeConverter } from '../../cartography/converters/map/map-link-node-to-link-node-converter';
|
||||||
import { ProjectMapMenuComponent } from './project-map-menu/project-map-menu.component';
|
import { ProjectMapMenuComponent } from './project-map-menu/project-map-menu.component';
|
||||||
import { ToasterService } from '../../services/toaster.service';
|
import { ToasterService } from '../../services/toaster.service';
|
||||||
|
import { ImportProjectDialogComponent } from '../projects/import-project-dialog/import-project-dialog.component';
|
||||||
|
import { MatDialog } from '@angular/material';
|
||||||
|
import { AddBlankProjectDialogComponent } from '../projects/add-blank-project-dialog/add-blank-project-dialog.component';
|
||||||
|
import { SaveProjectDialogComponent } from '../projects/save-project-dialog/save-project-dialog.component';
|
||||||
import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource, Indexed } from '../../cartography/datasources/map-datasource';
|
import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource, Indexed } from '../../cartography/datasources/map-datasource';
|
||||||
import { MapSettingsService } from '../../services/mapsettings.service';
|
import { MapSettingsService } from '../../services/mapsettings.service';
|
||||||
|
|
||||||
@ -120,6 +124,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
private mapScaleService: MapScaleService,
|
private mapScaleService: MapScaleService,
|
||||||
private nodeCreatedLabelStylesFixer: NodeCreatedLabelStylesFixer,
|
private nodeCreatedLabelStylesFixer: NodeCreatedLabelStylesFixer,
|
||||||
private toasterService: ToasterService,
|
private toasterService: ToasterService,
|
||||||
|
private dialog: MatDialog,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private mapNodesDataSource: MapNodesDataSource,
|
private mapNodesDataSource: MapNodesDataSource,
|
||||||
private mapLinksDataSource: MapLinksDataSource,
|
private mapLinksDataSource: MapLinksDataSource,
|
||||||
@ -433,6 +438,58 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
this.mapScaleService.resetToDefault();
|
this.mapScaleService.resetToDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addNewProject() {
|
||||||
|
const dialogRef = this.dialog.open(AddBlankProjectDialogComponent, {
|
||||||
|
width: '400px',
|
||||||
|
autoFocus: false
|
||||||
|
});
|
||||||
|
let instance = dialogRef.componentInstance;
|
||||||
|
instance.server = this.server;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveProject() {
|
||||||
|
const dialogRef = this.dialog.open(SaveProjectDialogComponent, {
|
||||||
|
width: '400px',
|
||||||
|
autoFocus: false
|
||||||
|
});
|
||||||
|
let instance = dialogRef.componentInstance;
|
||||||
|
instance.server = this.server;
|
||||||
|
instance.project = this.project;
|
||||||
|
}
|
||||||
|
|
||||||
|
importProject() {
|
||||||
|
let uuid: string = '';
|
||||||
|
const dialogRef = this.dialog.open(ImportProjectDialogComponent, {
|
||||||
|
width: '400px',
|
||||||
|
autoFocus: false
|
||||||
|
});
|
||||||
|
let instance = dialogRef.componentInstance;
|
||||||
|
instance.server = this.server;
|
||||||
|
const subscription = dialogRef.componentInstance.onImportProject.subscribe((projectId: string) => {
|
||||||
|
uuid = projectId;
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(() => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
this.projectService.open(this.server, uuid).subscribe(() => {
|
||||||
|
this.router.navigate(['/server', this.server.id, 'project', uuid]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exportProject() {
|
||||||
|
if (this.nodes.filter(node => node.node_type === 'virtualbox').length > 0) {
|
||||||
|
this.toasterService.error('Map with VirtualBox machines cannot be exported.')
|
||||||
|
} else if (this.nodes.filter(node =>
|
||||||
|
(node.status === 'started' && node.node_type==='vpcs') ||
|
||||||
|
(node.status === 'started' && node.node_type==='virtualbox') ||
|
||||||
|
(node.status === 'started' && node.node_type==='vmware')).length > 0) {
|
||||||
|
this.toasterService.error('Project with running nodes cannot be exported.')
|
||||||
|
} else {
|
||||||
|
location.assign(this.projectService.getExportPath(this.server, this.project));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public uploadImageFile(event) {
|
public uploadImageFile(event) {
|
||||||
this.readImageFile(event.target);
|
this.readImageFile(event.target);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, EventEmitter } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { MatDialog, MatDialogRef } from '@angular/material';
|
import { MatDialog, MatDialogRef } from '@angular/material';
|
||||||
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
|
||||||
@ -20,6 +20,8 @@ import { ToasterService } from '../../../services/toaster.service';
|
|||||||
export class AddBlankProjectDialogComponent implements OnInit {
|
export class AddBlankProjectDialogComponent implements OnInit {
|
||||||
server: Server;
|
server: Server;
|
||||||
projectNameForm: FormGroup;
|
projectNameForm: FormGroup;
|
||||||
|
uuid: string;
|
||||||
|
onAddProject = new EventEmitter<string>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<AddBlankProjectDialogComponent>,
|
public dialogRef: MatDialogRef<AddBlankProjectDialogComponent>,
|
||||||
@ -62,8 +64,9 @@ export class AddBlankProjectDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addProject(): void {
|
addProject(): void {
|
||||||
|
this.uuid = uuid();
|
||||||
this.projectService
|
this.projectService
|
||||||
.add(this.server, this.projectNameForm.controls['projectName'].value, uuid())
|
.add(this.server, this.projectNameForm.controls['projectName'].value, this.uuid)
|
||||||
.subscribe((project: Project) => {
|
.subscribe((project: Project) => {
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
this.toasterService.success(`Project ${project.name} added`);
|
this.toasterService.success(`Project ${project.name} added`);
|
||||||
|
@ -46,6 +46,14 @@ export class MockedProjectService {
|
|||||||
list() {
|
list() {
|
||||||
return of(this.projects);
|
return of(this.projects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUploadPath(server: Server, uuid: string, project_name: string) {
|
||||||
|
return `http://${server.host}:${server.port}/v2/projects/${uuid}/import?name=${project_name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getExportPath(server: Server, project: Project) {
|
||||||
|
return `http://${server.host}:${server.port}/v2/projects/${project.project_id}/export`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ImportProjectDialogComponent', () => {
|
describe('ImportProjectDialogComponent', () => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, Inject } from '@angular/core';
|
import { Component, OnInit, Inject, EventEmitter } from '@angular/core';
|
||||||
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material';
|
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material';
|
||||||
import { FileUploader, ParsedResponseHeaders, FileItem } from 'ng2-file-upload';
|
import { FileUploader, ParsedResponseHeaders, FileItem } from 'ng2-file-upload';
|
||||||
import { Server } from '../../../models/server';
|
import { Server } from '../../../models/server';
|
||||||
@ -26,6 +26,8 @@ export class ImportProjectDialogComponent implements OnInit {
|
|||||||
projectNameForm: FormGroup;
|
projectNameForm: FormGroup;
|
||||||
submitted: boolean = false;
|
submitted: boolean = false;
|
||||||
isFirstStepCompleted: boolean = false;
|
isFirstStepCompleted: boolean = false;
|
||||||
|
uuid: string;
|
||||||
|
onImportProject = new EventEmitter<string>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
@ -58,6 +60,7 @@ export class ImportProjectDialogComponent implements OnInit {
|
|||||||
status: number,
|
status: number,
|
||||||
headers: ParsedResponseHeaders
|
headers: ParsedResponseHeaders
|
||||||
) => {
|
) => {
|
||||||
|
this.onImportProject.emit(this.uuid);
|
||||||
this.resultMessage = 'Project was imported succesfully!';
|
this.resultMessage = 'Project was imported succesfully!';
|
||||||
this.isFinishEnabled = true;
|
this.isFinishEnabled = true;
|
||||||
};
|
};
|
||||||
@ -138,7 +141,8 @@ export class ImportProjectDialogComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prepareUploadPath(): string {
|
prepareUploadPath(): string {
|
||||||
|
this.uuid = uuid();
|
||||||
const projectName = this.projectNameForm.controls['projectName'].value;
|
const projectName = this.projectNameForm.controls['projectName'].value;
|
||||||
return `http://${this.server.host}:${this.server.port}/v2/projects/${uuid()}/import?name=${projectName}`;
|
return this.projectService.getUploadPath(this.server, uuid, projectName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
.file-name-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-snackbar {
|
||||||
|
background: #2196F3;
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<h1 mat-dialog-title>Save project as</h1>
|
||||||
|
<form [formGroup]="projectNameForm" class="file-name-form">
|
||||||
|
<mat-form-field class="file-name-form-field">
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
(keydown)="onKeyDown($event)"
|
||||||
|
type="text"
|
||||||
|
formControlName="projectName"
|
||||||
|
[ngClass]="{ 'is-invalid': form.projectName?.errors }"
|
||||||
|
placeholder="Please enter name"
|
||||||
|
/>
|
||||||
|
<mat-error *ngIf="form.projectName?.touched && form.projectName?.errors && form.projectName?.errors.required"
|
||||||
|
>Project name is required</mat-error
|
||||||
|
>
|
||||||
|
<mat-error *ngIf="form.projectName?.touched && form.projectName?.errors && form.projectName?.errors.invalidName"
|
||||||
|
>Project name is incorrect</mat-error
|
||||||
|
>
|
||||||
|
</mat-form-field>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-button (click)="onNoClick()" color="accent">Cancel</button>
|
||||||
|
<button mat-button (click)="onAddClick()" tabindex="2" mat-raised-button color="primary">Save project</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -0,0 +1,77 @@
|
|||||||
|
import { Component, OnInit, EventEmitter } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { MatDialog, MatDialogRef } from '@angular/material';
|
||||||
|
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
|
||||||
|
import { Project } from '../../../models/project';
|
||||||
|
import { Server } from '../../../models/server';
|
||||||
|
import { ProjectService } from '../../../services/project.service';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { ProjectNameValidator } from '../models/projectNameValidator';
|
||||||
|
import { ToasterService } from '../../../services/toaster.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-save-project-dialog',
|
||||||
|
templateUrl: './save-project-dialog.component.html',
|
||||||
|
styleUrls: ['./save-project-dialog.component.css'],
|
||||||
|
providers: [ProjectNameValidator]
|
||||||
|
})
|
||||||
|
export class SaveProjectDialogComponent implements OnInit {
|
||||||
|
server: Server;
|
||||||
|
project: Project;
|
||||||
|
projectNameForm: FormGroup;
|
||||||
|
onAddProject = new EventEmitter<string>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<SaveProjectDialogComponent>,
|
||||||
|
private router: Router,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private projectService: ProjectService,
|
||||||
|
private toasterService: ToasterService,
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private projectNameValidator: ProjectNameValidator
|
||||||
|
) {
|
||||||
|
this.projectNameForm = this.formBuilder.group({
|
||||||
|
projectName: new FormControl(null, [Validators.required, projectNameValidator.get])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
|
get form() {
|
||||||
|
return this.projectNameForm.controls;
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddClick(): void {
|
||||||
|
if (this.projectNameForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.projectService.list(this.server).subscribe((projects: Project[]) => {
|
||||||
|
const projectName = this.projectNameForm.controls['projectName'].value;
|
||||||
|
let existingProject = projects.find(project => project.name === projectName);
|
||||||
|
|
||||||
|
if (existingProject) {
|
||||||
|
this.toasterService.success(`Project with this name already exists.`);
|
||||||
|
} else {
|
||||||
|
this.addProject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onNoClick(): void {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
addProject(): void {
|
||||||
|
this.projectService.duplicate(this.server, this.project.project_id, this.projectNameForm.controls['projectName'].value).subscribe((project: Project) => {
|
||||||
|
this.dialogRef.close();
|
||||||
|
this.toasterService.success(`Project ${project.name} added`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event) {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
this.onAddClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -49,6 +49,18 @@ export class ProjectService {
|
|||||||
return this.httpServer.delete(server, `/projects/${project_id}`);
|
return this.httpServer.delete(server, `/projects/${project_id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUploadPath(server: Server, uuid: string, project_name: string) {
|
||||||
|
return `http://${server.host}:${server.port}/v2/projects/${uuid}/import?name=${project_name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getExportPath(server: Server, project: Project) {
|
||||||
|
return `http://${server.host}:${server.port}/v2/projects/${project.project_id}/export`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export(server: Server, project_id: string): Observable<any> {
|
||||||
|
return this.httpServer.get(server, `/projects/${project_id}/export`)
|
||||||
|
}
|
||||||
|
|
||||||
getStatistics(server: Server, project_id: string): Observable<any> {
|
getStatistics(server: Server, project_id: string): Observable<any> {
|
||||||
return this.httpServer.get(server, `/projects/${project_id}/stats`);
|
return this.httpServer.get(server, `/projects/${project_id}/stats`);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user