mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-06-17 06:18:09 +00:00
Fixes after review
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
import * as Raven from 'raven-js';
|
import * as Raven from 'raven-js';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { NgModule, ErrorHandler } from '@angular/core';
|
import { NgModule, ErrorHandler } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { CdkTableModule } from "@angular/cdk/table";
|
import { CdkTableModule } from "@angular/cdk/table";
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
|
||||||
@ -131,6 +131,7 @@ if (environment.production) {
|
|||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
CdkTableModule,
|
CdkTableModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
@ -1,9 +1,33 @@
|
|||||||
|
.non-visible {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-button{
|
||||||
|
height: 50px;
|
||||||
|
width: 120px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.file-name-form {
|
.file-name-form {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name-form-field {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
width:250px;
|
width:250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.non-visible {
|
.delete-button {
|
||||||
display: none;
|
background: transparent;
|
||||||
}
|
border: none;
|
||||||
|
outline: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-icon {
|
||||||
|
vertical-align: "middle";
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-message-box {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
@ -1,26 +1,34 @@
|
|||||||
<h1 mat-dialog-title>Import project</h1>
|
<h1 mat-dialog-title>Import project</h1>
|
||||||
<mat-horizontal-stepper #stepper [linear]="true">
|
<mat-horizontal-stepper #stepper [linear]="true">
|
||||||
<mat-step label="Choose file" editable="false" completed="false">
|
<mat-step label="Choose file" editable="false" completed="false">
|
||||||
<input type="file" accept=".gns3project, .gns3p" class="non-visible" #file (change)="uploadProjectFile($event)" ng2FileSelect [uploader]="uploader"/>
|
<div>
|
||||||
<button mat-raised-button color="primary" (click)="file.click()">Choose file</button>
|
<input type="file" accept=".gns3project, .gns3p" class="non-visible" #file (change)="uploadProjectFile($event)" ng2FileSelect [uploader]="uploader"/>
|
||||||
<mat-form-field class="file-name-form">
|
<button mat-raised-button color="primary" (click)="file.click()" class="file-button">Choose file</button>
|
||||||
<input matInput tabindex="1" (input)="validateInput()" [(ngModel)]="projectName" placeholder="Please enter name">
|
<form [formGroup]="projectNameForm" class="file-name-form">
|
||||||
</mat-form-field>
|
<mat-form-field class="file-name-form-field">
|
||||||
<button class="material-icons" (click)="onDeleteClick()">delete</button>
|
<input matInput type="text" formControlName="projectName" [ngClass]="{ 'is-invalid': form.projectName.errors }" placeholder="Please enter name" />
|
||||||
<div class="buttons-bar">
|
<mat-error *ngIf="form.projectName.errors && form.projectName.errors.required">Project name is required</mat-error>
|
||||||
<button mat-button (click)="onNoClick()" color="accent">Cancel</button>
|
<mat-error *ngIf="form.projectName.errors && form.projectName.errors.invalidName">Project name is incorrect</mat-error>
|
||||||
<button mat-button ng-disabled="!isImportEnabled" (click)="onImportClick()" tabindex="2" mat-raised-button color="primary">Import</button>
|
</mat-form-field>
|
||||||
|
<button class="delete-button">
|
||||||
|
<mat-icon color="primary" (click)="onDeleteClick()" class="delete-icon">clear</mat-icon>
|
||||||
|
</button>
|
||||||
|
<div class="buttons-bar">
|
||||||
|
<button mat-button (click)="onNoClick()" color="accent">Cancel</button>
|
||||||
|
<button mat-button [disabled]="!isImportEnabled" (click)="onImportClick()" tabindex="2" mat-raised-button color="primary">Import</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
<mat-step label="Progress" editable="false" completed="false">
|
<mat-step label="Progress" editable="false" completed="false">
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': uploader.progress + '%' }"></div>
|
<div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': uploader.progress + '%' }"></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="result-message-box">
|
||||||
<span>{{errorMessage}}</span>
|
<span>{{resultMessage}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons-bar">
|
<div class="buttons-bar">
|
||||||
<button mat-button ng-disabled="!isFinishEnabled" (click)="onNoClick()" tabindex="2" mat-raised-button color="primary">Finish</button>
|
<button mat-button [disabled]="!isFinishEnabled" (click)="onNoClick()" tabindex="2" mat-raised-button color="primary">Finish</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
</mat-horizontal-stepper>
|
</mat-horizontal-stepper>
|
@ -1,11 +1,11 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { ImportProjectDialogComponent } from "./import-project-dialog.component";
|
import { ImportProjectDialogComponent, Validator } from "./import-project-dialog.component";
|
||||||
import { Server } from "../../../models/server";
|
import { Server } from "../../../models/server";
|
||||||
import { MatInputModule, MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule, MatStepperModule, MatFormFieldModule, MatDialogRef, MatDialog, MAT_DIALOG_DATA } from "@angular/material";
|
import { MatInputModule, MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule, MatStepperModule, MatFormFieldModule, MatDialogRef, MatDialog, MAT_DIALOG_DATA } from "@angular/material";
|
||||||
import { RouterTestingModule } from "@angular/router/testing";
|
import { RouterTestingModule } from "@angular/router/testing";
|
||||||
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
|
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
|
||||||
import { FileUploadModule, FileSelectDirective, FileItem, FileUploader } from "ng2-file-upload";
|
import { FileUploadModule, FileSelectDirective, FileItem, FileUploader } from "ng2-file-upload";
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule, FormBuilder, FormControl, Validators } from '@angular/forms';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
@ -13,9 +13,9 @@ describe('ImportProjectDialogComponent', () => {
|
|||||||
let component: ImportProjectDialogComponent;
|
let component: ImportProjectDialogComponent;
|
||||||
let fixture: ComponentFixture<ImportProjectDialogComponent>;
|
let fixture: ComponentFixture<ImportProjectDialogComponent>;
|
||||||
let server: Server;
|
let server: Server;
|
||||||
let dialog: MatDialog;
|
|
||||||
let debugElement: DebugElement;
|
let debugElement: DebugElement;
|
||||||
let fileSelectDirective: FileSelectDirective;
|
let fileSelectDirective: FileSelectDirective;
|
||||||
|
let formBuilder: FormBuilder;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -31,6 +31,7 @@ describe('ImportProjectDialogComponent', () => {
|
|||||||
NoopAnimationsModule,
|
NoopAnimationsModule,
|
||||||
FileUploadModule,
|
FileUploadModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
RouterTestingModule.withRoutes([]),
|
RouterTestingModule.withRoutes([]),
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
@ -41,10 +42,10 @@ describe('ImportProjectDialogComponent', () => {
|
|||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
dialog = TestBed.get(MatDialog);
|
|
||||||
server = new Server();
|
server = new Server();
|
||||||
server.ip = "localhost";
|
server.ip = "localhost";
|
||||||
server.port = 80;
|
server.port = 80;
|
||||||
|
formBuilder = new FormBuilder();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -52,7 +53,10 @@ describe('ImportProjectDialogComponent', () => {
|
|||||||
debugElement = fixture.debugElement;
|
debugElement = fixture.debugElement;
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.server = server;
|
component.server = server;
|
||||||
|
component.projectNameForm = formBuilder.group({
|
||||||
|
projectName: new FormControl(null, [Validators.required, Validator.projectNameValidator])
|
||||||
|
});
|
||||||
|
component.projectNameForm.controls['projectName'].setValue("ValidName");
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
debugElement = fixture.debugElement.query(By.directive(FileSelectDirective));
|
debugElement = fixture.debugElement.query(By.directive(FileSelectDirective));
|
||||||
@ -119,16 +123,16 @@ describe('ImportProjectDialogComponent', () => {
|
|||||||
|
|
||||||
it('should prepare correct upload path for file', () => {
|
it('should prepare correct upload path for file', () => {
|
||||||
fileSelectDirective.uploader.queue.push(new FileItem(fileSelectDirective.uploader,new File([],"fileName"),{}));
|
fileSelectDirective.uploader.queue.push(new FileItem(fileSelectDirective.uploader,new File([],"fileName"),{}));
|
||||||
component.projectName = "newProject.gns3";
|
component.projectNameForm.controls['projectName'].setValue("newProject");
|
||||||
|
|
||||||
component.prepareUploadPath();
|
component.onImportClick();
|
||||||
|
|
||||||
expect(fileSelectDirective.uploader.queue[0].url).toContain("localhost:80");
|
expect(fileSelectDirective.uploader.queue[0].url).toContain("localhost:80");
|
||||||
expect(fileSelectDirective.uploader.queue[0].url).toContain("newProject");
|
expect(fileSelectDirective.uploader.queue[0].url).toContain("newProject");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should navigate to next step after clicking import', () => {
|
it('should navigate to next step after clicking import', () => {
|
||||||
let fileItem = new FileItem(fileSelectDirective.uploader,new File([],"fileName"),{});
|
let fileItem = new FileItem(fileSelectDirective.uploader, new File([],"fileName"),{});
|
||||||
fileSelectDirective.uploader.queue.push(fileItem);
|
fileSelectDirective.uploader.queue.push(fileItem);
|
||||||
spyOn(component.stepper, "next");
|
spyOn(component.stepper, "next");
|
||||||
|
|
||||||
@ -136,4 +140,30 @@ describe('ImportProjectDialogComponent', () => {
|
|||||||
|
|
||||||
expect(component.stepper.next).toHaveBeenCalled();
|
expect(component.stepper.next).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should detect if file input is empty', () => {
|
||||||
|
component.projectNameForm.controls['projectName'].setValue("");
|
||||||
|
fixture.detectChanges();
|
||||||
|
spyOn(component.stepper, "next");
|
||||||
|
spyOn(fileSelectDirective.uploader, 'uploadItem');
|
||||||
|
|
||||||
|
component.onImportClick();
|
||||||
|
|
||||||
|
expect(component.stepper.next).not.toHaveBeenCalled();
|
||||||
|
expect(fileSelectDirective.uploader.uploadItem).not.toHaveBeenCalled();
|
||||||
|
expect(component.projectNameForm.valid).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should sanitize file name input', () => {
|
||||||
|
component.projectNameForm.controls['projectName'].setValue("[][]");
|
||||||
|
fixture.detectChanges();
|
||||||
|
spyOn(component.stepper, "next");
|
||||||
|
spyOn(fileSelectDirective.uploader, 'uploadItem');
|
||||||
|
|
||||||
|
component.onImportClick();
|
||||||
|
|
||||||
|
expect(component.stepper.next).not.toHaveBeenCalled();
|
||||||
|
expect(fileSelectDirective.uploader.uploadItem).not.toHaveBeenCalled();
|
||||||
|
expect(component.projectNameForm.valid).toBeFalsy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,51 +3,80 @@ import { MatStepper, MatDialogRef, MAT_DIALOG_DATA } 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';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
|
||||||
|
|
||||||
|
export class Validator {
|
||||||
|
static projectNameValidator(projectName) {
|
||||||
|
var pattern = new RegExp(/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/);
|
||||||
|
|
||||||
|
if(!pattern.test(projectName.value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { invalidName: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-import-project-dialog',
|
selector: 'app-import-project-dialog',
|
||||||
templateUrl: 'import-project-dialog.component.html',
|
templateUrl: 'import-project-dialog.component.html',
|
||||||
styleUrls: ['import-project-dialog.component.css'],
|
styleUrls: ['import-project-dialog.component.css']
|
||||||
})
|
})
|
||||||
export class ImportProjectDialogComponent implements OnInit {
|
export class ImportProjectDialogComponent implements OnInit {
|
||||||
uploader: FileUploader;
|
uploader: FileUploader;
|
||||||
server : Server;
|
server : Server;
|
||||||
projectName : string;
|
|
||||||
isImportEnabled : boolean = false;
|
isImportEnabled : boolean = false;
|
||||||
isFinishEnabled : boolean = false;
|
isFinishEnabled : boolean = false;
|
||||||
errorMessage : string;
|
resultMessage : string = "The project is being imported... Please wait";
|
||||||
|
projectNameForm: FormGroup;
|
||||||
|
submitted: boolean = false;
|
||||||
|
|
||||||
@ViewChild('stepper') stepper: MatStepper;
|
@ViewChild('stepper') stepper: MatStepper;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<ImportProjectDialogComponent>,
|
public dialogRef: MatDialogRef<ImportProjectDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: any){}
|
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||||
|
private formBuilder: FormBuilder){
|
||||||
|
this.projectNameForm = this.formBuilder.group({
|
||||||
|
projectName: new FormControl(null, [Validators.required, Validator.projectNameValidator])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(){
|
ngOnInit(){
|
||||||
this.uploader = new FileUploader({});
|
this.uploader = new FileUploader({});
|
||||||
this.uploader.onAfterAddingFile = (file) => { file.withCredentials = false; };
|
this.uploader.onAfterAddingFile = (file) => { file.withCredentials = false; };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get form() {
|
||||||
|
return this.projectNameForm.controls;
|
||||||
|
}
|
||||||
|
|
||||||
uploadProjectFile(event) : void{
|
uploadProjectFile(event) : void{
|
||||||
this.projectName = event.target.files[0].name.split(".")[0];
|
this.projectNameForm.controls['projectName'].setValue(event.target.files[0].name.split(".")[0]);
|
||||||
this.isImportEnabled = true;
|
this.isImportEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
onImportClick() : void{
|
onImportClick() : void{
|
||||||
if(this.validateProjectName()){
|
if (this.projectNameForm.invalid){
|
||||||
this.prepareUploadPath();
|
this.submitted = true;
|
||||||
|
} else {
|
||||||
|
const url = this.prepareUploadPath();
|
||||||
|
this.uploader.queue.forEach(elem => elem.url = url);
|
||||||
|
|
||||||
this.stepper.selected.completed = true;
|
this.stepper.selected.completed = true;
|
||||||
this.stepper.next();
|
this.stepper.next();
|
||||||
let itemToUpload = this.uploader.queue[0];
|
|
||||||
|
const itemToUpload = this.uploader.queue[0];
|
||||||
this.uploader.uploadItem(itemToUpload);
|
this.uploader.uploadItem(itemToUpload);
|
||||||
|
|
||||||
this.uploader.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {
|
this.uploader.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {
|
||||||
this.errorMessage = response;
|
this.resultMessage = response;
|
||||||
this.isFinishEnabled = true;
|
this.isFinishEnabled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.uploader.onSuccessItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {
|
this.uploader.onSuccessItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {
|
||||||
this.isFinishEnabled = true;
|
this.resultMessage = "Project was imported succesfully!";
|
||||||
|
this.isFinishEnabled = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,16 +93,11 @@ export class ImportProjectDialogComponent implements OnInit {
|
|||||||
onDeleteClick() : void{
|
onDeleteClick() : void{
|
||||||
this.uploader.queue.pop();
|
this.uploader.queue.pop();
|
||||||
this.isImportEnabled = false;
|
this.isImportEnabled = false;
|
||||||
this.projectName = "";
|
this.projectNameForm.controls['projectName'].setValue("");
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareUploadPath() : void{
|
prepareUploadPath() : string{
|
||||||
let url = `http://${this.server.ip}:${this.server.port}/v2/projects/${uuid()}/import?name=${this.projectName}`;
|
const projectName = this.projectNameForm.controls['projectName'].value;
|
||||||
this.uploader.queue.forEach(elem => elem.url = url);
|
return `http://${this.server.ip}:${this.server.port}/v2/projects/${uuid()}/import?name=${projectName}`;
|
||||||
}
|
|
||||||
|
|
||||||
validateProjectName() : boolean{
|
|
||||||
var pattern = new RegExp(/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/);
|
|
||||||
return !pattern.test(this.projectName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user