Permission object, display object name instead of uuid

This commit is contained in:
Sylvain MATHIEU 2022-01-21 15:36:58 +01:00
parent 6a573110e8
commit 707f5b6c7f
17 changed files with 454 additions and 325 deletions

View File

@ -311,6 +311,7 @@ import { AddRoleToGroupComponent } from './components/group-details/add-role-to-
import {MatFormFieldModule} from "@angular/material/form-field"; import {MatFormFieldModule} from "@angular/material/form-field";
import { PermissionsFilterPipe } from './components/permissions-management/permissions-filter.pipe'; import { PermissionsFilterPipe } from './components/permissions-management/permissions-filter.pipe';
import { PermissionsTypeFilterPipe } from './components/permissions-management/permissions-type-filter.pipe'; import { PermissionsTypeFilterPipe } from './components/permissions-management/permissions-type-filter.pipe';
import { DisplayPathPipe } from './components/permissions-management/display-path.pipe';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -533,7 +534,9 @@ import { PermissionsTypeFilterPipe } from './components/permissions-management/p
FilterCompletePipe, FilterCompletePipe,
UserPermissionsComponent, UserPermissionsComponent,
PermissionsFilterPipe, PermissionsFilterPipe,
PermissionsTypeFilterPipe PermissionsTypeFilterPipe,
FilterCompletePipe,
DisplayPathPipe
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -685,5 +688,6 @@ import { PermissionsTypeFilterPipe } from './components/permissions-management/p
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule { export class AppModule {
constructor(protected _googleAnalyticsService: GoogleAnalyticsService) {} constructor(protected _googleAnalyticsService: GoogleAnalyticsService) {
}
} }

View File

@ -30,7 +30,7 @@ export class AddPermissionLineComponent implements OnInit {
action: PermissionActions.ALLOW, action: PermissionActions.ALLOW,
description: "", description: "",
methods: [], methods: [],
path: "/", path: "/"
}; };
edit = false; edit = false;

View File

@ -0,0 +1,44 @@
import {SubPath} from "./SubPath";
export class PermissionPath {
private subPath: SubPath[] = [];
constructor() {
}
add(subPath: SubPath) {
this.subPath.push(subPath);
}
getDisplayPath() {
return this.subPath
.map((subPath) => subPath.displayValue);
}
removeLast() {
this.subPath.pop();
}
getPath() {
return this.subPath.map((subPath) => subPath.value);
}
isEmpty() {
return this.subPath.length === 0;
}
getVariables(): { key: string; value: string }[] {
return this.subPath
.filter((path) => path.key)
.map((path) => {
return {key: path.key, value: path.value};
});
}
containStar() {
return this.subPath
.map(subPath => subPath.value === '*')
.reduce((previous, next) => previous || next, false);
}
}

View File

@ -0,0 +1,6 @@
export class SubPath {
constructor(public value: string,
public displayValue: string,
public key?: string) {
}
}

View File

@ -1,6 +1,6 @@
<div class="path"> <div class="path">
<div>Path: /</div> <div>Path: /</div>
<div *ngFor="let p of path">{{p}}/</div> <div *ngFor="let p of path.getDisplayPath()">{{p}}/</div>
<div class="path-edit-line"> <div class="path-edit-line">
<div> <div>
<div *ngIf="mode === 'SELECT'"> <div *ngIf="mode === 'SELECT'">
@ -17,21 +17,21 @@
[matAutocomplete]="auto"> [matAutocomplete]="auto">
<mat-autocomplete #auto="matAutocomplete"> <mat-autocomplete #auto="matAutocomplete">
<mat-option [value]="'*'">*</mat-option> <mat-option [value]="'*'">*</mat-option>
<mat-option *ngFor="let data of completeData | filterComplete: completeField" <mat-option *ngFor="let data of completeData.data | filterComplete: completeField"
[value]="data.id"> [value]="data.name">
<span>{{data.name}}</span> | <span>{{data.name}}</span>
<small>{{data.id}}</small>
</mat-option> </mat-option>
</mat-autocomplete> </mat-autocomplete>
</div> </div>
</div> </div>
<div class="command-button"> <div class="command-button">
<mat-icon (click)="removePrevious()" *ngIf="path.length > 0">cancel</mat-icon> <mat-icon (click)="removePrevious()" *ngIf="!path.isEmpty()">cancel</mat-icon>
<mat-icon (click)="getNext()" *ngIf="!this.mode">add_circle_outline</mat-icon> <mat-icon (click)="getNext()" *ngIf="!this.mode">add_circle_outline</mat-icon>
<mat-icon <mat-icon
matTooltip="validate data" matTooltip="validate data"
(click)="validComplete()" (click)="validComplete()"
*ngIf="this.mode === 'COMPLETE'">check_circle</mat-icon> *ngIf="this.mode === 'COMPLETE'">check_circle
</mat-icon>
</div> </div>
</div> </div>
</div> </div>

View File

@ -13,6 +13,8 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {ApiInformationService, IFormatedList} from "@services/api-information.service"; import {ApiInformationService, IFormatedList} from "@services/api-information.service";
import {Server} from "@models/server"; import {Server} from "@models/server";
import {PermissionPath} from "@components/permissions-management/add-permission-line/path-auto-complete/PermissionPath";
import {SubPath} from "@components/permissions-management/add-permission-line/path-auto-complete/SubPath";
@Component({ @Component({
selector: 'app-path-auto-complete', selector: 'app-path-auto-complete',
@ -24,9 +26,9 @@ export class PathAutoCompleteComponent implements OnInit {
@Output() update = new EventEmitter<string>(); @Output() update = new EventEmitter<string>();
@Input() server: Server; @Input() server: Server;
path: string[] = []; path: PermissionPath = new PermissionPath();
values: string[] = []; values: string[] = [];
private completeData: IFormatedList[]; private completeData: { data: IFormatedList[]; key: string };
public mode: 'SELECT' | 'COMPLETE' | undefined; public mode: 'SELECT' | 'COMPLETE' | undefined;
completeField: string; completeField: string;
@ -34,14 +36,14 @@ export class PathAutoCompleteComponent implements OnInit {
} }
updatePath(value: string) { updatePath(name: string, value: string, key?: string) {
this.path.push(value); this.path.add(new SubPath(name, value, key));
this.update.emit(`/${this.path.join('/')}`); this.update.emit('/' + this.path.getPath().join("/"));
} }
popPath() { popPath() {
this.path.pop(); this.path.removeLast();
this.update.emit(`/${this.path.join('/')}`); this.update.emit('/' + this.path.getPath().join("/"));
} }
ngOnInit(): void { ngOnInit(): void {
@ -49,8 +51,11 @@ export class PathAutoCompleteComponent implements OnInit {
getNext() { getNext() {
this.apiInformationService this.apiInformationService
.getPathNextElement(this.path) .getPathNextElement(this.path.getPath())
.subscribe((next: string[]) => { .subscribe((next: string[]) => {
if (this.path.containStar()) {
next = next.filter(item => !item.match(this.apiInformationService.bracketIdRegex));
}
this.values = next; this.values = next;
this.mode = 'SELECT'; this.mode = 'SELECT';
}); });
@ -60,27 +65,33 @@ export class PathAutoCompleteComponent implements OnInit {
if (this.mode) { if (this.mode) {
return this.mode = undefined; return this.mode = undefined;
} }
if (this.path.length > 0) { if (!this.path.isEmpty()) {
return this.popPath(); return this.popPath();
} }
} }
valueChanged(value: string) { valueChanged(value: string) {
if (value.match(this.apiInformationService.bracketIdRegex)) { if (value.match(this.apiInformationService.bracketIdRegex) && !this.path.containStar()) {
this.apiInformationService.getListByObjectId(this.server, value) this.apiInformationService.getListByObjectId(this.server, value, undefined, this.path.getVariables())
.subscribe((data) => { .subscribe((data) => {
this.mode = 'COMPLETE'; this.mode = 'COMPLETE';
this.completeData = data; this.completeData = {data, key: value};
}); });
} else { } else {
this.updatePath(value); this.updatePath(value, value);
this.mode = undefined; this.mode = undefined;
} }
} }
validComplete() { validComplete() {
this.updatePath(this.completeField); if (this.completeField === '*') {
this.updatePath('*', '*');
} else {
const data = this.completeData.data.find((d) => this.completeField === d.name);
this.updatePath(data.id, data.name, this.completeData.key);
}
this.mode = undefined; this.mode = undefined;
this.completeField = undefined;
} }
} }

View File

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

View File

@ -0,0 +1,42 @@
import {Pipe, PipeTransform} from '@angular/core';
import {map, switchMap} from "rxjs/operators";
import {forkJoin, Observable, of} from "rxjs";
import {ApiInformationService} from "@services/api-information.service";
import {Server} from "@models/server";
@Pipe({
name: 'displayPath'
})
export class DisplayPathPipe implements PipeTransform {
constructor(private apiInformation: ApiInformationService) {
}
transform(originalPath: string, server: Server): Observable<string> {
if (!server) {
return of(originalPath);
}
return this.apiInformation
.getKeysForPath(originalPath)
.pipe(switchMap((values) => {
if (values.length === 0) {
return of([]);
}
const obs = values.map((k) => this.apiInformation.getListByObjectId(server, k.key, k.value, values));
return forkJoin(obs);
}),
map((values: { id: string; name: string }[][]) => {
let displayPath = `${originalPath}`;
values.forEach((value) => {
if (value[0].id && value[0].name) {
displayPath = displayPath.replace(value[0].id, value[0].name);
} else {
}
});
return displayPath;
})
);
}
}

View File

@ -18,9 +18,9 @@
</div> </div>
<div> <div>
<div <div
[matTooltip]="permission.path" [matTooltip]="permission.path | displayPath: server | async"
matTooltipClass="custom-tooltip"> matTooltipClass="custom-tooltip">
{{permission.path}} {{permission.path | displayPath: server | async}}
</div> </div>
</div> </div>
<div> <div>

View File

@ -33,6 +33,7 @@
<div *ngFor="let permission of permissions | permissionsTypeFilter: typeFilter?.view | permissionsFilter: searchPermissions?.id | paginator: pageEvent"> <div *ngFor="let permission of permissions | permissionsTypeFilter: typeFilter?.view | permissionsFilter: searchPermissions?.id | paginator: pageEvent">
<app-permission-add-edit-line <app-permission-add-edit-line
[permission]="permission" [permission]="permission"
[server]="server"
(update)="refresh()"></app-permission-add-edit-line> (update)="refresh()"></app-permission-add-edit-line>
</div> </div>
<mat-paginator [length]="permissions.length" (page)="pageEvent = $event" <mat-paginator [length]="permissions.length" (page)="pageEvent = $event"

View File

@ -6,7 +6,7 @@
</button> </button>
<div class="content"> <div class="content">
<div class="center">{{permission.methods.join(",")}}</div> <div class="center">{{permission.methods.join(",")}}</div>
<div class="center">{{permission.path}}</div> <div class="center">{{permission.path | displayPath: server | async}}</div>
</div> </div>
<button *ngIf="side === 'LEFT'" mat-button (click)="onClick()"> <button *ngIf="side === 'LEFT'" mat-button (click)="onClick()">
<mat-icon>keyboard_arrow_right</mat-icon> <mat-icon>keyboard_arrow_right</mat-icon>

View File

@ -12,6 +12,7 @@
*/ */
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {Permission} from "@models/api/permission"; import {Permission} from "@models/api/permission";
import { Server } from '@models/server';
@Component({ @Component({
selector: 'app-editable-permission', selector: 'app-editable-permission',
@ -21,6 +22,7 @@ import {Permission} from "@models/api/permission";
export class EditablePermissionComponent implements OnInit { export class EditablePermissionComponent implements OnInit {
@Input() permission: Permission; @Input() permission: Permission;
@Input() server: Server;
@Input() side: 'LEFT' | 'RIGHT'; @Input() side: 'LEFT' | 'RIGHT';
@Output() click = new EventEmitter(); @Output() click = new EventEmitter();
@ -38,7 +40,7 @@ export class EditablePermissionComponent implements OnInit {
return ` return `
action: ${this.permission.action} action: ${this.permission.action}
methods: ${this.permission.methods.join(',')} methods: ${this.permission.methods.join(',')}
path: ${this.permission.path} original path: ${this.permission.path}
`; `;
} }
} }

View File

@ -29,6 +29,7 @@
<app-editable-permission <app-editable-permission
[side]="'LEFT'" [side]="'LEFT'"
[permission]="permission" [permission]="permission"
[server]="server"
(click)="remove(permission)" (click)="remove(permission)"
*ngFor="let permission of owned"> *ngFor="let permission of owned">
@ -40,6 +41,7 @@
<app-editable-permission <app-editable-permission
[side]="'RIGHT'" [side]="'RIGHT'"
[permission]="permission" [permission]="permission"
[server]="server"
(click)="add(permission)" (click)="add(permission)"
*ngFor="let permission of available"> *ngFor="let permission of available">

View File

@ -51,6 +51,7 @@
</div> </div>
<app-editable-permission <app-editable-permission
[permission]="permission" [permission]="permission"
[server]="server"
*ngFor="let permission of role.permissions"> *ngFor="let permission of role.permissions">
</app-editable-permission> </app-editable-permission>
</div> </div>

View File

@ -197,6 +197,7 @@ export class ApiInformationService {
.filter(elem => !(elem === '' || elem === 'v3')); .filter(elem => !(elem === '' || elem === 'v3'));
return paths[0].subPaths.map((elem, index) => { return paths[0].subPaths.map((elem, index) => {
if (elem.match(this.bracketIdRegex)) { if (elem.match(this.bracketIdRegex)) {
return {key: elem, value: splinted[index]}; return {key: elem, value: splinted[index]};
} }
}); });
@ -205,7 +206,8 @@ export class ApiInformationService {
})); }));
} }
getListByObjectId(server: Server, key: string, value?: string) { getListByObjectId(server: Server, key: string, value?: string, extraParams?: { key: string; value: string }[]) {
const idName = /{([^)]+)}/.exec(key)[1];
function findElement(data: IApiObject[]): IApiObject { function findElement(data: IApiObject[]): IApiObject {
const elem = data.find(d => d.name === key); const elem = data.find(d => d.name === key);
if (!elem) { if (!elem) {
@ -218,9 +220,16 @@ export class ApiInformationService {
map(findElement), map(findElement),
switchMap(elem => { switchMap(elem => {
let url = `${server.protocol}//${server.host}:${server.port}${elem.path}`; let url = `${server.protocol}//${server.host}:${server.port}${elem.path}`;
if (extraParams) {
extraParams.forEach((param) => {
url = url.replace(param.key, param.value);
});
}
if (value) { if (value) {
url = `${url}/${value}`; url = `${url}/${value}`;
} }
return this.httpClient.get<any[]>(url, {headers: {Authorization: `Bearer ${server.authToken}`}}); return this.httpClient.get<any[]>(url, {headers: {Authorization: `Bearer ${server.authToken}`}});
} }
), ),
@ -235,21 +244,19 @@ export class ApiInformationService {
const nameKey = keys.find(k => k.match(/name/)); const nameKey = keys.find(k => k.match(/name/));
response = response.map(o => { response = response.map(o => {
return { return {
id: o[idKey], id: o[idName] || o[idKey],
name: o[nameKey] name: o[nameKey]
}; };
}); });
response.forEach(elt => { response.forEach(elt => {
this.cache_permissions[elt.id] = elt; this.cache_permissions[elt.id] = elt;
}) });
console.log('b', this.cache_permissions)
return of(response); return of(response);
} else { } else {
const keys = Object.keys(response); const keys = Object.keys(response);
const idKey = keys.find(k => k.match(/_id$|filename/)); const idKey = keys.find(k => k.match(/_id$|filename/));
const nameKey = keys.find(k => k.match(/name/)); const nameKey = keys.find(k => k.match(/name/));
const ret = {id: response[idKey], name: response[nameKey]}; const ret = {id: response[idName] || response[idKey], name: response[nameKey]};
this.cache_permissions[ret.id] = ret; this.cache_permissions[ret.id] = ret;
return of([ret]); return of([ret]);
} }

View File

@ -21,7 +21,8 @@ import {Observable} from "rxjs/Rx";
}) })
export class PermissionsService { export class PermissionsService {
constructor(private httpServer: HttpServer) { } constructor(private httpServer: HttpServer) {
}
list(server: Server) { list(server: Server) {
return this.httpServer.get<Permission[]>(server, '/permissions'); return this.httpServer.get<Permission[]>(server, '/permissions');