Permissions management, add paginator and filters

This commit is contained in:
Lebeau Elise 2022-01-25 14:17:54 +00:00 committed by Sylvain MATHIEU
parent 0d5f11dfc1
commit 6a573110e8
19 changed files with 299 additions and 55 deletions

View File

@ -1,5 +1,4 @@
/* tslint:disable */
/* tslint:disable:max-line-length */
import {DragDropModule} from '@angular/cdk/drag-drop';
import {OverlayModule} from '@angular/cdk/overlay';
import {CdkTableModule} from '@angular/cdk/table';
@ -300,7 +299,6 @@ import { PermissionEditorValidateDialogComponent } from './components/role-manag
import { PermissionsManagementComponent } from './components/permissions-management/permissions-management.component';
import { PermissionEditLineComponent } from '@components/permissions-management/permission-edit-line/permission-edit-line.component';
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
import { RolePermissionsComponent } from './components/role-management/role-detail/role-permissions/role-permissions.component';
import { UserPermissionsComponent } from './components/user-management/user-detail/user-permissions/user-permissions.component';
import {MatAutocompleteModule} from "@angular/material/autocomplete";
import {PathAutoCompleteComponent} from './components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component';
@ -311,6 +309,8 @@ import { ActionButtonComponent } from './components/permissions-management/actio
import { DeletePermissionDialogComponent } from './components/permissions-management/delete-permission-dialog/delete-permission-dialog.component';
import { AddRoleToGroupComponent } from './components/group-details/add-role-to-group/add-role-to-group.component';
import {MatFormFieldModule} from "@angular/material/form-field";
import { PermissionsFilterPipe } from './components/permissions-management/permissions-filter.pipe';
import { PermissionsTypeFilterPipe } from './components/permissions-management/permissions-type-filter.pipe';
@NgModule({
declarations: [
@ -531,8 +531,9 @@ import {MatFormFieldModule} from "@angular/material/form-field";
DeletePermissionDialogComponent,
PathAutoCompleteComponent,
FilterCompletePipe,
RolePermissionsComponent,
UserPermissionsComponent
UserPermissionsComponent,
PermissionsFilterPipe,
PermissionsTypeFilterPipe
],
imports: [
BrowserModule,

View File

@ -19,21 +19,21 @@ import {PageEvent} from "@angular/material/paginator";
})
export class PaginatorPipe implements PipeTransform {
transform(members: User[] | undefined, paginatorEvent: PageEvent | undefined): User[] {
if (!members) {
transform<T>(elements: T[] | undefined, paginatorEvent: PageEvent | undefined): T[] {
if (!elements) {
return [];
}
if (!paginatorEvent) {
paginatorEvent = {
length: members.length,
length: elements.length,
pageIndex: 0,
pageSize: 5
};
}
return members.slice(
return elements.slice(
paginatorEvent.pageIndex * paginatorEvent.pageSize,
(paginatorEvent.pageIndex + 1) * paginatorEvent.pageSize);
}

View File

@ -21,23 +21,23 @@
<table mat-table [dataSource]="dataSource | groupFilter: searchText" class="mat-elevation-z8" matSort>
<ng-container matColumnDef="select" >
<mat-header-cell *matHeaderCellDef class="small-col">
<th mat-header-cell *matHeaderCellDef class="small-col">
<mat-checkbox (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</mat-header-cell>
<mat-cell *matCellDef="let row" class="small-col">
</th>
<td mat-cell *matCellDef="let row" class="small-col">
<mat-checkbox (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
</mat-checkbox>
</mat-cell>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name</th>
<td mat-cell *matCellDef="let element"><a routerLink="/server/{{server.id}}/management/groups/{{element.user_group_id}}">{{element.name}}</a> </td>
<td mat-cell *matCellDef="let element"><a class="table-link" routerLink="/server/{{server.id}}/management/groups/{{element.user_group_id}}">{{element.name}}</a> </td>
</ng-container>
<ng-container matColumnDef="created_at">
@ -47,7 +47,7 @@
<ng-container matColumnDef="updated_at">
<th mat-header-cell *matHeaderCellDef mat-sort-header> last update</th>
<th mat-header-cell *matHeaderCellDef mat-sort-header> Last update</th>
<td mat-cell *matCellDef="let element"> {{element.updated_at}} </td>
</ng-container>

View File

@ -1,3 +1,15 @@
/*
* Software Name : GNS3 Web UI
* Version: 3
* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This software is distributed under the GPL-3.0 or any later version,
* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
* or see the "LICENSE" file for more details.
*
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import { Pipe, PipeTransform } from '@angular/core';
import {IFormatedList} from "@services/api-information.service";

View File

@ -1,3 +1,15 @@
/*
* Software Name : GNS3 Web UI
* Version: 3
* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This software is distributed under the GPL-3.0 or any later version,
* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
* or see the "LICENSE" file for more details.
*
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {ApiInformationService, IFormatedList} from "@services/api-information.service";
import {Server} from "@models/server";

View File

@ -0,0 +1,8 @@
import { PermissionsFilterPipe } from './permissions-filter.pipe';
describe('PermissionsFilterPipe', () => {
it('create an instance', () => {
const pipe = new PermissionsFilterPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,32 @@
/*
* Software Name : GNS3 Web UI
* Version: 3
* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This software is distributed under the GPL-3.0 or any later version,
* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
* or see the "LICENSE" file for more details.
*
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import { Pipe, PipeTransform } from '@angular/core';
import {Permission} from "@models/api/permission";
@Pipe({
name: 'permissionsFilter'
})
export class PermissionsFilterPipe implements PipeTransform {
transform(permissions: Permission[], filterText: string): Permission[] {
if (!permissions) {
return [];
}
if (filterText === undefined || filterText === null || filterText === '') {
return permissions;
}
return permissions.filter((permissions: Permission) => permissions.path.toLowerCase().includes(filterText.toLowerCase()));
}
}

View File

@ -5,14 +5,38 @@
(addPermissionEvent)="refresh()"></app-add-permission-line>
</div>
<div class="permission-content default-content">
<!--<ng-template #dynamic></ng-template>-->
<app-add-permission-line [server]="server" (addPermissionEvent)="updateList($event)"
*ngIf="newPermissionEdit"></app-add-permission-line>
<div *ngFor="let permission of permissions">
Filter :
<mat-select [(ngModel)]="typeFilter" name="type" placeholder="Type"
(selectionChange)="changeType($event)"
class="permission-filter">
<mat-option [value]="{value:'/', view:'/'}" selected>All</mat-option>
<mat-option *ngFor="let elt of typeValues" [value]="elt">{{elt.view}}</mat-option>
</mat-select>
<input type="text"
matInput
name="typeInput"
placeholder="Search by name"
[(ngModel)]="searchPermissions"
[matAutocomplete]="auto"
class="permission-filter"
(input)="changeAutocomplete($event.target.value)">
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
<mat-option *ngFor="let option of filteredOptions | filterComplete: searchPermissions" [value]="option">
{{option.name}}
</mat-option>
</mat-autocomplete>
<div *ngFor="let permission of permissions | permissionsTypeFilter: typeFilter?.view | permissionsFilter: searchPermissions?.id | paginator: pageEvent">
<app-permission-add-edit-line
[permission]="permission"
(update)="refresh()"></app-permission-add-edit-line>
</div>
<mat-paginator [length]="permissions.length" (page)="pageEvent = $event"
[pageSizeOptions]="[5, 20, 50, 100]"></mat-paginator>
</div>
</div>
<ng-template #loading>

View File

@ -28,3 +28,10 @@
border-bottom: 1px solid;
align-items: center;*/
}
.permission-filter {
border-bottom: 1px solid;
width : 200px;
margin: 5px;
border-bottom-color: #b0bec5;
}

View File

@ -18,6 +18,8 @@ import {ProgressService} from "../../common/progress/progress.service";
import {Permission} from "@models/api/permission";
import {AddPermissionLineComponent} from "@components/permissions-management/add-permission-line/add-permission-line.component";
import {ServerService} from "@services/server.service";
import {PageEvent} from "@angular/material/paginator";
import {ApiInformationService, IFormatedList} from "@services/api-information.service";
@Component({
selector: 'app-permissions-management',
@ -29,6 +31,16 @@ export class PermissionsManagementComponent implements OnInit {
permissions: Permission[];
addPermissionLineComp = AddPermissionLineComponent;
newPermissionEdit = false;
searchPermissions: any;
pageEvent: PageEvent | undefined;
filteredOptions: IFormatedList[];
options: string[] = [];
typeFilter: any;
typeValues = [
{value: '{project_id}', view:'projects'},
{value: '{image_path}', view:'images'},
{value: '{template_id}', view:'templates'},
{value: '{compute_id}', view:'computes'}]
@ViewChild('dynamic', {
read: ViewContainerRef
@ -39,11 +51,11 @@ export class PermissionsManagementComponent implements OnInit {
private router: Router,
private permissionService: PermissionsService,
private progressService: ProgressService,
private serverService: ServerService) { }
private serverService: ServerService,
private apiInformationService: ApiInformationService) { }
ngOnInit(): void {
const serverId = this.route.parent.snapshot.paramMap.get('server_id');
console.log(serverId);
this.serverService.get(+serverId).then((server: Server) => {
this.server = server;
this.refresh();
@ -62,15 +74,24 @@ export class PermissionsManagementComponent implements OnInit {
);
}
addPermission() {
//const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.addPermissionLineComp);
//const component = this.viewContainerRef.createComponent(componentFactory);
//component.instance.server = this.server;
this.newPermissionEdit = true;
changeType(typeValue: any) {
if (typeValue.value.value.match(this.apiInformationService.bracketIdRegex)) {
this.apiInformationService.getListByObjectId(this.server, typeValue.value.value)
.subscribe((data) => {
this.filteredOptions = data;
});
} else {
this.filteredOptions = this.apiInformationService.getIdByObjNameFromCache('');
}
}
updateList($event: any) {
this.newPermissionEdit = false;
displayFn(value): string {
return value && value.name ? value.name : '';
}
changeAutocomplete(inputText) {
this.filteredOptions = this.apiInformationService.getIdByObjNameFromCache(inputText);
}
}

View File

@ -0,0 +1,8 @@
import { PermissionsTypeFilterPipe } from './permissions-type-filter.pipe';
describe('PermissionsTypeFilterPipe', () => {
it('create an instance', () => {
const pipe = new PermissionsTypeFilterPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -0,0 +1,32 @@
/*
* Software Name : GNS3 Web UI
* Version: 3
* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This software is distributed under the GPL-3.0 or any later version,
* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
* or see the "LICENSE" file for more details.
*
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import { Pipe, PipeTransform } from '@angular/core';
import {Permission} from "@models/api/permission";
@Pipe({
name: 'permissionsTypeFilter'
})
export class PermissionsTypeFilterPipe implements PipeTransform {
transform(permissions: Permission[], filterTypeText: string): Permission[] {
if (!permissions) {
return [];
}
if (filterTypeText === undefined || filterTypeText === null || filterTypeText === '') {
return permissions;
}
return permissions.filter((permissions: Permission) => permissions.path.toLowerCase().includes(filterTypeText.toLowerCase()));
}
}

View File

@ -36,7 +36,7 @@
</div>
<mat-divider [vertical]="true"></mat-divider>
<div class="column">
<div class="title">available</div>
<div class="title">Available</div>
<app-editable-permission
[side]="'RIGHT'"
[permission]="permission"

View File

@ -1,3 +1,15 @@
/*
* Software Name : GNS3 Web UI
* Version: 3
* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This software is distributed under the GPL-3.0 or any later version,
* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
* or see the "LICENSE" file for more details.
*
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import { Component, OnInit } from '@angular/core';
import {ActivatedRoute, Router} from "@angular/router";
import {MatDialog} from "@angular/material/dialog";

View File

@ -77,7 +77,6 @@ export class UserDetailComponent implements OnInit {
this.userService.update(this.server, updatedUser)
.subscribe((user: User) => {
console.log("Done ", user)
this.toasterService.success(`User ${user.username} updated`);
},
(error) => {

View File

@ -1,3 +1,15 @@
/*
* Software Name : GNS3 Web UI
* Version: 3
* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This software is distributed under the GPL-3.0 or any later version,
* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
* or see the "LICENSE" file for more details.
*
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import { Injectable } from '@angular/core';
import {
Router, Resolve,

View File

@ -1,3 +1,15 @@
/*
* Software Name : GNS3 Web UI
* Version: 3
* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This software is distributed under the GPL-3.0 or any later version,
* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
* or see the "LICENSE" file for more details.
*
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import { Injectable } from '@angular/core';
import {
Router, Resolve,

View File

@ -1,3 +1,15 @@
/*
* Software Name : GNS3 Web UI
* Version: 3
* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
* SPDX-License-Identifier: GPL-3.0-or-later
*
* This software is distributed under the GPL-3.0 or any later version,
* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
* or see the "LICENSE" file for more details.
*
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import { Injectable } from '@angular/core';
import {
Router, Resolve,

View File

@ -12,8 +12,8 @@
*/
import {Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {Observable, ReplaySubject} from "rxjs";
import {map, switchMap} from "rxjs/operators";
import {Observable, of, ReplaySubject} from "rxjs";
import {map, switchMap, take} from "rxjs/operators";
import {Methods} from "@models/api/permission";
import {HttpServer} from "@services/http-server.service";
import {Server} from "@models/server";
@ -40,19 +40,22 @@ export interface IFormatedList {
name?: string;
}
@Injectable({
providedIn: 'root'
})
export class ApiInformationService {
private cache_permissions: { [key: string]: IFormatedList; } = {};
private allowed = ['projects', 'images', 'templates', 'computes', 'symbols', 'notifications'];
private data: ReplaySubject<IPathDict[]> = new ReplaySubject<IPathDict[]>(1);
private objs: ReplaySubject<IApiObject[]> = new ReplaySubject<IApiObject[]>(1);
public readonly bracketIdRegex = new RegExp("\{(.*?)\}");
public readonly bracketIdRegex = new RegExp("\{(.*?)\}", 'g');
public readonly uuidRegex = new RegExp("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}");
public readonly finalBracketIdRegex = new RegExp("\{(.*?)\}$");
constructor(private httpClient: HttpClient,
private httpServer: HttpServer) {
constructor(private httpClient: HttpClient) {
this.loadLocalInformation();
this.data.subscribe((data) => {
@ -143,7 +146,6 @@ export class ApiInformationService {
const splinted = path
.split('/')
.filter(elem => !(elem === '' || elem === 'v3'));
let remains = data;
splinted.forEach((value, index) => {
if (value === '*') {
@ -154,9 +156,7 @@ export class ApiInformationService {
if (matchUrl.length === 0) {
matchUrl = remains.filter(val => val.subPaths[index]?.match(this.bracketIdRegex));
}
remains = matchUrl;
});
return remains;
})
@ -189,10 +189,25 @@ export class ApiInformationService {
}));
}
getListByObjectId(server: Server, value: string) {
getKeysForPath(path: string): Observable<{ key: string; value: string }[]> {
return this.getPath(path)
.pipe(map((paths: IPathDict[]) => {
const splinted = path
.split('/')
.filter(elem => !(elem === '' || elem === 'v3'));
return paths[0].subPaths.map((elem, index) => {
if (elem.match(this.bracketIdRegex)) {
return {key: elem, value: splinted[index]};
}
});
}), map((values) => {
return values.filter((v) => v !== undefined);
}));
}
getListByObjectId(server: Server, key: string, value?: string) {
function findElement(data: IApiObject[]): IApiObject {
const elem = data.find(d => d.name === value);
const elem = data.find(d => d.name === key);
if (!elem) {
throw new Error('entry not found');
}
@ -202,28 +217,53 @@ export class ApiInformationService {
return this.objs.pipe(
map(findElement),
switchMap(elem => {
const url = `${server.protocol}//${server.host}:${server.port}${elem.path}`;
let url = `${server.protocol}//${server.host}:${server.port}${elem.path}`;
if (value) {
url = `${url}/${value}`;
}
return this.httpClient.get<any[]>(url, {headers: {Authorization: `Bearer ${server.authToken}`}});
}
),
map(response => {
switchMap(response => {
if (response.length === 0) {
return [];
if (response instanceof Array) {
if (response.length === 0) {
return of([]);
}
const keys = Object.keys(response[0]);
const idKey = keys.find(k => k.match(/_id$|filename/));
const nameKey = keys.find(k => k.match(/name/));
response = response.map(o => {
return {
id: o[idKey],
name: o[nameKey]
};
});
response.forEach(elt => {
this.cache_permissions[elt.id] = elt;
})
console.log('b', this.cache_permissions)
return of(response);
} else {
const keys = Object.keys(response);
const idKey = keys.find(k => k.match(/_id$|filename/));
const nameKey = keys.find(k => k.match(/name/));
const ret = {id: response[idKey], name: response[nameKey]};
this.cache_permissions[ret.id] = ret;
return of([ret]);
}
}), take(1));
}
const keys = Object.keys(response[0]);
const idKey = keys.find(k => k.match(/_id$|filename/));
const nameKey = keys.find(k => k.match(/name/));
return response.map(o => {
return {
id: o[idKey],
name: o[nameKey]
};
});
}));
getIdByObjNameFromCache(name: string): IFormatedList[] {
const ret: IFormatedList[] = [];
for (let [key, value] of Object.entries(this.cache_permissions)) {
if (value.name.includes(name)) {
ret.push(value);
}
}
return ret;
}
}