mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-01-30 16:13:55 +00:00
I have improved on the projects page like this
1.Allow to delete multiple projects at once (similar to what is done in the image manager). 2. Add an export project action in the actions column. The export icon could be improved. 3.Replace "Duplicate" action by "Save project as" (with same icon as on project settings menu). 4.Delete 'Go to system status' button and add 'System status' entry to top right menu. 5.Delete 'Go to preferences' button and add 'Template preferences' entry to top right menu. 6.Add "Image manager" entry to the top right menu
This commit is contained in:
parent
f106ca51da
commit
8c444059ad
@ -280,6 +280,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: [
|
||||
@ -476,6 +477,7 @@ import { NodesMenuConfirmationDialogComponent } from './components/project-map/n
|
||||
UploadingProcessbarComponent,
|
||||
ExportPortableProjectComponent,
|
||||
NodesMenuConfirmationDialogComponent,
|
||||
ConfirmationDeleteAllProjectsComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
@ -999,6 +999,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.exportPortableProjectDialog();
|
||||
}
|
||||
}
|
||||
|
||||
exportPortableProjectDialog() {
|
||||
const dialogRef = this.dialog.open(ExportPortableProjectComponent, {
|
||||
width: '700px',
|
||||
|
@ -0,0 +1,34 @@
|
||||
<div *ngIf="!isDelete && !isUsedFiles">
|
||||
<h1 mat-dialog-title>Do you want delete all files ?.</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>Your selected files</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,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ConfirmationDeleteAllProjectsComponent } from './confirmation-delete-all-projects.component';
|
||||
|
||||
describe('ConfirmationDeleteAllProjectsComponent', () => {
|
||||
let component: ConfirmationDeleteAllProjectsComponent;
|
||||
let fixture: ComponentFixture<ConfirmationDeleteAllProjectsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
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.server, 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,8 @@
|
||||
<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 (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 +22,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">
|
||||
@ -52,22 +67,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>
|
||||
|
||||
|
@ -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 {
|
||||
server: Server;
|
||||
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;
|
||||
|
||||
@ -188,6 +192,70 @@ export class ProjectsComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteAllFiles() {
|
||||
const dialogRef = this.dialog.open(ConfirmationDeleteAllProjectsComponent, {
|
||||
width: '550px',
|
||||
maxHeight: '650px',
|
||||
autoFocus: false,
|
||||
disableClose: true,
|
||||
data: {
|
||||
server: this.server,
|
||||
deleteFilesPaths: this.selection.selected
|
||||
}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((isAllfilesdeleted: boolean) => {
|
||||
if (isAllfilesdeleted) {
|
||||
this.unChecked()
|
||||
this.refresh()
|
||||
this.toasterService.success('All files 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: {serverDetails:this.server,projectDetails:this.project},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((isAddes: boolean) => {});
|
||||
}
|
||||
}
|
||||
|
||||
export class ProjectDatabase {
|
||||
@ -208,6 +276,8 @@ export class ProjectDatabase {
|
||||
this.dataChange.next(this.data.slice());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class ProjectDataSource extends DataSource<any> {
|
||||
|
@ -19,6 +19,19 @@
|
||||
</button>
|
||||
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item (click)="goToSystemStatus()">
|
||||
<mat-icon>info</mat-icon>
|
||||
<span>System status</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="goToPreferences()">
|
||||
<mat-icon>settings_applications</mat-icon>
|
||||
<span>Template preferences</span>
|
||||
</button>
|
||||
<button mat-menu-item>
|
||||
<!-- [routerLink]="['/controller', controller.id, 'image-manager']" -->
|
||||
<mat-icon>collections</mat-icon>
|
||||
<span>Image manager</span>
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/settings">
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span>Settings</span>
|
||||
|
@ -29,6 +29,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
||||
recentlyOpenedServerId: string;
|
||||
recentlyOpenedProjectId: string;
|
||||
serverIdProjectList: string;
|
||||
controller: Server;
|
||||
|
||||
constructor(
|
||||
private electronService: ElectronService,
|
||||
@ -37,11 +38,15 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
||||
private toasterService: ToasterService,
|
||||
private progressService: ProgressService,
|
||||
private router: Router,
|
||||
private serverService: ServerService
|
||||
private serverService: ServerService,
|
||||
private route: ActivatedRoute,
|
||||
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.checkIfUserIsLoginPage();
|
||||
this.controller = this.route.snapshot.data['server'];
|
||||
|
||||
this.routeSubscription = this.router.events.subscribe((val) => {
|
||||
if (val instanceof NavigationEnd) this.checkIfUserIsLoginPage();
|
||||
});
|
||||
@ -110,6 +115,18 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
||||
.catch((error) => this.toasterService.error('Cannot navigate to the last opened project'));
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
@HostListener('window:beforeunload', ['$event'])
|
||||
async onBeforeUnload($event) {
|
||||
if (!this.shouldStopServersOnClosing) {
|
||||
@ -124,6 +141,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy {
|
||||
window.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
ngOnDestroy() {
|
||||
this.serverStatusSubscription.unsubscribe();
|
||||
|
Loading…
x
Reference in New Issue
Block a user