mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-01-29 23:54:11 +00:00
commit
73bc8cd4b7
@ -92,9 +92,9 @@ const routes: Routes = [
|
||||
canActivate: [LoginGuard],
|
||||
resolve: { controller: ControllerResolve },
|
||||
},
|
||||
{ path: 'help', component: HelpComponent },
|
||||
{ path: 'settings', component: SettingsComponent },
|
||||
{ path: 'settings/console', component: ConsoleComponent },
|
||||
{ path: 'controller/:controller_id/help', component: HelpComponent },
|
||||
{ path: 'controller/:controller_id/settings', component: SettingsComponent },
|
||||
{ path: 'controller/:controller_id/settings/console', component: ConsoleComponent },
|
||||
{
|
||||
path: 'controller/:controller_id/management/users/:user_id',
|
||||
component: UserDetailComponent,
|
||||
|
@ -322,6 +322,7 @@ import { DeleteAllImageFilesDialogComponent } from './components/image-manager/d
|
||||
import { UploadingProcessbarComponent } from './common/uploading-processbar/uploading-processbar.component';
|
||||
import { ExportPortableProjectComponent } from './components/export-portable-project/export-portable-project.component';
|
||||
import { NodesMenuConfirmationDialogComponent } from './components/project-map/nodes-menu/nodes-menu-confirmation-dialog/nodes-menu-confirmation-dialog.component';
|
||||
import { ConfirmationDeleteAllProjectsComponent } from './components/projects/confirmation-delete-all-projects/confirmation-delete-all-projects.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -559,6 +560,7 @@ import { NodesMenuConfirmationDialogComponent } from './components/project-map/n
|
||||
UploadingProcessbarComponent,
|
||||
ExportPortableProjectComponent,
|
||||
NodesMenuConfirmationDialogComponent,
|
||||
ConfirmationDeleteAllProjectsComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
@ -998,6 +998,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.exportPortableProjectDialog();
|
||||
}
|
||||
}
|
||||
|
||||
exportPortableProjectDialog() {
|
||||
const dialogRef = this.dialog.open(ExportPortableProjectComponent, {
|
||||
width: '700px',
|
||||
|
@ -1,4 +1,4 @@
|
||||
<h1 mat-dialog-title>Please choose name for exporting project</h1>
|
||||
<h1 mat-dialog-title>Save project as</h1>
|
||||
|
||||
<div class="modal-form-container">
|
||||
<mat-form-field class="form-field">
|
||||
@ -8,5 +8,5 @@
|
||||
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onCloseClick()" color="accent">Cancel</button>
|
||||
<button mat-button (click)="onSaveClick()" tabindex="2" mat-raised-button color="primary">Apply</button>
|
||||
<button mat-button (click)="onSaveClick()" tabindex="2" mat-raised-button color="primary">Save project</button>
|
||||
</div>
|
||||
|
@ -0,0 +1,34 @@
|
||||
<div *ngIf="!isDelete && !isUsedFiles">
|
||||
<h1 mat-dialog-title>Do you want delete all projects?</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>Your selected projects</p>
|
||||
<p *ngFor="let file of deleteData?.deleteFilesPaths; let i = index">{{i+1}}. {{file?.filename}}</p>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="deleteAll()">Delete</button>
|
||||
<button mat-button mat-dialog-close cdkFocusInitial>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isDelete && !isUsedFiles">
|
||||
<h1 align="center" mat-dialog-title>Please wait.</h1>
|
||||
|
||||
<div mat-dialog-content align="center">
|
||||
<mat-spinner color="accent"></mat-spinner>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isDelete && isUsedFiles">
|
||||
<div mat-dialog-content>
|
||||
<div *ngIf="deleteFliesDetails.length > 0">
|
||||
<h5>Project can't be deleted because image used in one or more template.</h5>
|
||||
<p *ngFor="let message of deleteFliesDetails; let i = index" [ngClass]="{'deleted-error-text': message?.error?.message}"><span *ngIf="message !=null">{{i+1}}. {{message?.error?.message}}</span></p>
|
||||
</div>
|
||||
<div *ngIf="fileNotDeleted.length > 0">
|
||||
<h5 class="delete-text">{{fileNotDeleted.length}} Projects deleted successfully.</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-raised-button color="primary" (click)="dialogRef.close(false)">Close</button>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,54 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MockedProjectService } from '../../../services/project.service.spec';
|
||||
import { MockedToasterService } from '../../../services/toaster.service.spec';
|
||||
import { ProjectService } from '../../../services/project.service';
|
||||
import { ControllerService } from '../../../services/controller.service';
|
||||
import { MockedControllerService } from '../../../services/controller.service.spec';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
|
||||
import { ConfirmationDeleteAllProjectsComponent } from './confirmation-delete-all-projects.component';
|
||||
|
||||
describe('ConfirmationDeleteAllProjectsComponent', () => {
|
||||
let component: ConfirmationDeleteAllProjectsComponent;
|
||||
let fixture: ComponentFixture<ConfirmationDeleteAllProjectsComponent>;
|
||||
let mockedControllerService = new MockedControllerService();
|
||||
let mockedImageManagerService = new MockedProjectService()
|
||||
let mockedToasterService = new MockedToasterService()
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MatIconModule,
|
||||
MatToolbarModule,
|
||||
MatMenuModule,
|
||||
MatCheckboxModule,
|
||||
MatDialogModule,
|
||||
],
|
||||
providers: [
|
||||
{ provide: ControllerService, useValue: mockedControllerService },
|
||||
{ provide: ProjectService, useValue: mockedImageManagerService },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: {} },
|
||||
{ provide: MatDialogRef, useValue: {} },
|
||||
{ provide: ToasterService, useValue: mockedToasterService },
|
||||
|
||||
],
|
||||
declarations: [ ConfirmationDeleteAllProjectsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConfirmationDeleteAllProjectsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { ProjectService } from '../../../services/project.service';
|
||||
import { ToasterService } from '../../../services/toaster.service';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-confirmation-delete-all-projects',
|
||||
templateUrl: './confirmation-delete-all-projects.component.html',
|
||||
styleUrls: ['./confirmation-delete-all-projects.component.scss']
|
||||
})
|
||||
export class ConfirmationDeleteAllProjectsComponent implements OnInit {
|
||||
isDelete: boolean = false;
|
||||
isUsedFiles: boolean = false;
|
||||
deleteFliesDetails: any = []
|
||||
fileNotDeleted: any = []
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public deleteData: any,
|
||||
public dialogRef: MatDialogRef<ConfirmationDeleteAllProjectsComponent>,
|
||||
private projectService: ProjectService,
|
||||
private toasterService: ToasterService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
async deleteAll() {
|
||||
this.isDelete = true
|
||||
await this.deleteFile()
|
||||
}
|
||||
|
||||
deleteFile() {
|
||||
const calls = [];
|
||||
this.deleteData.deleteFilesPaths.forEach(project => {
|
||||
calls.push(this.projectService.delete(this.deleteData.controller, project.project_id).pipe(catchError(error => of(error))))
|
||||
});
|
||||
Observable.forkJoin(calls).subscribe(responses => {
|
||||
this.deleteFliesDetails = responses.filter(x => x !== null)
|
||||
this.fileNotDeleted = responses.filter(x => x === null)
|
||||
this.isUsedFiles = true;
|
||||
this.isDelete = true
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -2,8 +2,6 @@
|
||||
<div class="default-header">
|
||||
<div class="row">
|
||||
<h1 class="col">Projects</h1>
|
||||
<button class="col" mat-raised-button (click)="goToSystemStatus()" class="add-button">Go to system status</button>
|
||||
<button class="col" mat-raised-button (click)="goToPreferences()" class="add-button">Go to preferences</button>
|
||||
<button class="col" mat-raised-button color="primary" (click)="addBlankProject()" class="add-button">
|
||||
Add blank project
|
||||
</button>
|
||||
@ -22,6 +20,21 @@
|
||||
<div class="default-content">
|
||||
<div class="mat-elevation-z8">
|
||||
<mat-table #table [dataSource]="dataSource | projectsfilter: searchText" matSort>
|
||||
<ng-container matColumnDef="select">
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox (change)="$event ? selectAllImages() : null" [checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
|
||||
[checked]="selection.isSelected(row)">
|
||||
</mat-checkbox>
|
||||
</mat-cell>
|
||||
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
@ -30,8 +43,8 @@
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell *matHeaderCellDef> Actions </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" style="text-align: right">
|
||||
<mat-header-cell *matHeaderCellDef class="action"> Actions </mat-header-cell>
|
||||
<mat-cell class="action" *matCellDef="let row" style="text-align: right">
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="Open project"
|
||||
@ -52,22 +65,37 @@
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="Duplicate project"
|
||||
matTooltip="Save project as"
|
||||
matTooltipClass="custom-tooltip"
|
||||
(click)="duplicate(row)"
|
||||
*ngIf="row.status == 'closed'"
|
||||
>
|
||||
<mat-icon aria-label="Duplicate project">filter_2</mat-icon>
|
||||
<mat-icon aria-label="Save project as">save</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="Delete project"
|
||||
matTooltip="Export project"
|
||||
matTooltipClass="custom-tooltip"
|
||||
(click)="delete(row)"
|
||||
*ngIf="row.status == 'closed'"
|
||||
(click)="exportSelectProject(row)"
|
||||
|
||||
>
|
||||
<mat-icon aria-label="Delete project">delete</mat-icon>
|
||||
<mat-icon aria-label="Export project">arrow_downward</mat-icon>
|
||||
|
||||
</button>
|
||||
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="delete" >
|
||||
<mat-header-cell *matHeaderCellDef class="action">
|
||||
<button mat-button matTooltip="Delete all projects" matTooltipClass="custom-tooltip"*ngIf="(selection.hasValue() && isAllSelected()) || selection.selected.length > 1" (click)="deleteAllFiles()" aria-label="Example icon button with a delete icon">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" class="action">
|
||||
<button mat-icon-button matTooltip="Delete project" matTooltipClass="custom-tooltip" (click)="delete(row)" *ngIf="row.status == 'closed' && selection.isSelected(row)" aria-label="Example icon button with a delete icon" >
|
||||
<mat-icon aria-label="Delete project">delete</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
|
@ -19,3 +19,14 @@
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// mat-header-cell, mat-cell {
|
||||
// justify-content: center;
|
||||
// }
|
||||
|
||||
.action{
|
||||
justify-content: center;
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { DataSource, SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatBottomSheet } from '@angular/material/bottom-sheet';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSort, MatSortable } from '@angular/material/sort';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ExportPortableProjectComponent } from '../../components/export-portable-project/export-portable-project.component';
|
||||
import { ElectronService } from 'ngx-electron';
|
||||
import { BehaviorSubject, merge, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs//operators';
|
||||
@ -17,6 +18,7 @@ import { ToasterService } from '../../services/toaster.service';
|
||||
import { AddBlankProjectDialogComponent } from './add-blank-project-dialog/add-blank-project-dialog.component';
|
||||
import { ChooseNameDialogComponent } from './choose-name-dialog/choose-name-dialog.component';
|
||||
import { ConfirmationBottomSheetComponent } from './confirmation-bottomsheet/confirmation-bottomsheet.component';
|
||||
import { ConfirmationDeleteAllProjectsComponent } from './confirmation-delete-all-projects/confirmation-delete-all-projects.component';
|
||||
import { ImportProjectDialogComponent } from './import-project-dialog/import-project-dialog.component';
|
||||
import { NavigationDialogComponent } from './navigation-dialog/navigation-dialog.component';
|
||||
|
||||
@ -29,10 +31,12 @@ export class ProjectsComponent implements OnInit {
|
||||
controller:Controller ;
|
||||
projectDatabase = new ProjectDatabase();
|
||||
dataSource: ProjectDataSource;
|
||||
displayedColumns = ['name', 'actions'];
|
||||
displayedColumns = ['select', 'name', 'actions', 'delete'];
|
||||
settings: Settings;
|
||||
|
||||
project: Project;
|
||||
searchText: string = '';
|
||||
isAllDelete: boolean = false;
|
||||
selection = new SelectionModel(true, []);
|
||||
|
||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
||||
|
||||
@ -65,18 +69,6 @@ export class ProjectsComponent implements OnInit {
|
||||
this.projectService.projectListSubject.subscribe(() => this.refresh());
|
||||
}
|
||||
|
||||
goToPreferences() {
|
||||
this.router
|
||||
.navigate(['/controller', this.controller.id, 'preferences'])
|
||||
.catch((error) => this.toasterService.error('Cannot navigate to the preferences'));
|
||||
}
|
||||
|
||||
goToSystemStatus() {
|
||||
this.router
|
||||
.navigate(['/controller', this.controller.id, 'systemstatus'])
|
||||
.catch((error) => this.toasterService.error('Cannot navigate to the system status'));
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.projectService.list(this.controller).subscribe(
|
||||
(projects: Project[]) => {
|
||||
@ -188,6 +180,70 @@ export class ProjectsComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteAllFiles() {
|
||||
const dialogRef = this.dialog.open(ConfirmationDeleteAllProjectsComponent, {
|
||||
width: '550px',
|
||||
maxHeight: '650px',
|
||||
autoFocus: false,
|
||||
disableClose: true,
|
||||
data: {
|
||||
controller: this.controller,
|
||||
deleteFilesPaths: this.selection.selected
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((isAllfilesdeleted: boolean) => {
|
||||
if (isAllfilesdeleted) {
|
||||
this.unChecked()
|
||||
this.refresh()
|
||||
this.toasterService.success('All projects deleted');
|
||||
} else {
|
||||
this.unChecked()
|
||||
this.refresh()
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isAllSelected() {
|
||||
const numSelected = this.selection.selected.length;
|
||||
const numRows = this.projectDatabase.data.length;
|
||||
return numSelected === numRows;
|
||||
}
|
||||
|
||||
selectAllImages() {
|
||||
this.isAllSelected() ? this.unChecked() : this.allChecked();
|
||||
}
|
||||
|
||||
unChecked() {
|
||||
this.selection.clear();
|
||||
this.isAllDelete = false;
|
||||
}
|
||||
|
||||
allChecked() {
|
||||
this.projectDatabase.data.forEach((row) => this.selection.select(row));
|
||||
this.isAllDelete = true;
|
||||
}
|
||||
|
||||
exportSelectProject(project: Project){
|
||||
this.project = project
|
||||
if(this.project.project_id){
|
||||
this.exportPortableProjectDialog()
|
||||
}
|
||||
|
||||
}
|
||||
exportPortableProjectDialog() {
|
||||
const dialogRef = this.dialog.open(ExportPortableProjectComponent, {
|
||||
width: '700px',
|
||||
maxHeight: '850px',
|
||||
autoFocus: false,
|
||||
disableClose: true,
|
||||
data: {controllerDetails:this.controller,projectDetails:this.project},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((isAddes: boolean) => {});
|
||||
}
|
||||
}
|
||||
|
||||
export class ProjectDatabase {
|
||||
|
@ -19,7 +19,29 @@
|
||||
</button>
|
||||
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item routerLink="/settings">
|
||||
<button mat-menu-item
|
||||
[disabled]="!controllerId"
|
||||
[routerLink]="['controller', controllerId, 'systemstatus']">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>System status</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
|
||||
[disabled]="!controllerId"
|
||||
[routerLink]="['controller', controllerId, 'settings']"
|
||||
>
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
@ -29,7 +51,10 @@
|
||||
<mat-icon>groups</mat-icon>
|
||||
<span>Management</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/help">
|
||||
<button mat-menu-item
|
||||
[disabled]="!controllerId"
|
||||
[routerLink]="['controller', controllerId, 'help']"
|
||||
>
|
||||
<mat-icon>help</mat-icon>
|
||||
<span>Help</span>
|
||||
</button>
|
||||
|
@ -73,7 +73,6 @@ export class ProjectService {
|
||||
}
|
||||
|
||||
links(controller:Controller , project_id: string) {
|
||||
debugger
|
||||
return this.httpController.get<Link[]>(controller, `/projects/${project_id}/links`);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user