mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2024-12-21 13:57:48 +00:00
create UI to manege privileges to a role
This commit is contained in:
parent
9ca10d6d6a
commit
2c0cd88ca6
@ -309,6 +309,7 @@ import { ExportPortableProjectComponent } from './components/export-portable-pro
|
||||
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';
|
||||
import { ProjectMapLockConfirmationDialogComponent } from './components/project-map/project-map-menu/project-map-lock-confirmation-dialog/project-map-lock-confirmation-dialog.component';
|
||||
import { PrivilegeComponent } from './components/role-management/role-detail/privilege/privilege.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -531,6 +532,7 @@ import { ProjectMapLockConfirmationDialogComponent } from './components/project-
|
||||
NodesMenuConfirmationDialogComponent,
|
||||
ConfirmationDeleteAllProjectsComponent,
|
||||
ProjectMapLockConfirmationDialogComponent,
|
||||
PrivilegeComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
@ -0,0 +1,4 @@
|
||||
export interface IPrivilegesChange {
|
||||
add: string[];
|
||||
delete: string[];
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<link rel="stylesheet" href="privilege.component.scss">
|
||||
<div class="privileges">
|
||||
<div class="header">
|
||||
<div class="title">Privileges</div>
|
||||
<div *ngIf="!disable">
|
||||
<button mat-button *ngIf="!editMode">
|
||||
<mat-icon (click)="editMode = true">edit</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="editMode" mat-raised-button color="warn" (click)="editMode = false">Cancel</button>
|
||||
<button *ngIf="editMode" mat-raised-button color="primary" (click)="close()">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngFor="let privilege of privileges">
|
||||
<div class="privilege">
|
||||
<mat-checkbox
|
||||
*ngIf="editMode"
|
||||
[checked]="ownedPrivilegesList.includes(privilege.privilege_id)"
|
||||
(change)="onPrivilegeChange($event.checked, privilege)"></mat-checkbox>
|
||||
<div *ngIf="editMode; else actualPrivileges">
|
||||
<div
|
||||
[ngClass]="{add: changer.isAdded(privilege.privilege_id), delete: changer.isDeleted(privilege.privilege_id)}">
|
||||
{{privilege.name}}</div>
|
||||
</div>
|
||||
<ng-template #actualPrivileges>
|
||||
<div *ngIf="ownedPrivilegesList.includes(privilege.privilege_id)">{{privilege.name}}</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,32 @@
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.header > div > button {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.privilege {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: 10px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.add {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.delete {
|
||||
color: red;
|
||||
text-decoration: line-through;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PrivilegeComponent } from './privilege.component';
|
||||
|
||||
describe('PrivilegeComponent', () => {
|
||||
let component: PrivilegeComponent;
|
||||
let fixture: ComponentFixture<PrivilegeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ PrivilegeComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PrivilegeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,47 @@
|
||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
||||
import {Privilege} from "@models/api/Privilege";
|
||||
import {PrivilegeChange} from "@components/role-management/role-detail/privilege/privilegeChange";
|
||||
import {IPrivilegesChange} from "@components/role-management/role-detail/privilege/IPrivilegesChange";
|
||||
|
||||
@Component({
|
||||
selector: 'app-privilege',
|
||||
templateUrl: './privilege.component.html',
|
||||
styleUrls: ['./privilege.component.scss']
|
||||
})
|
||||
export class PrivilegeComponent implements OnInit {
|
||||
|
||||
@Input() disable = true;
|
||||
@Input() privileges: Privilege[] = [];
|
||||
@Input() ownedPrivilegesList: string[] = []
|
||||
@Output() update: EventEmitter<IPrivilegesChange> = new EventEmitter<IPrivilegesChange>();
|
||||
|
||||
changer = new PrivilegeChange(this.ownedPrivilegesList);
|
||||
private editModeState = false;
|
||||
|
||||
get editMode(): boolean {
|
||||
return this.editModeState;
|
||||
};
|
||||
set editMode(state: boolean) {
|
||||
if(state) {
|
||||
this.changer = new PrivilegeChange(this.ownedPrivilegesList);
|
||||
}
|
||||
this.editModeState = state;
|
||||
};
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
onPrivilegeChange(checked: boolean, privilege: Privilege) {
|
||||
const id = privilege.privilege_id
|
||||
if(checked) {
|
||||
this.changer.add(id);
|
||||
} else {
|
||||
this.changer.delete(id);
|
||||
}
|
||||
}
|
||||
close() {
|
||||
this.update.emit(this.changer.get())
|
||||
this.editMode = false;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
import {PrivilegeChange} from "@components/role-management/role-detail/privilege/privilegeChange";
|
||||
|
||||
const one = "8a6cda1a-9842-4cbe-94da-a7eb97562c96";
|
||||
const two = "97acaefe-b378-46d0-937e-e9aa22cf8a93";
|
||||
const three = "8be17c2f-4d83-4492-a494-f354dbd8cc4f";
|
||||
const four = "aaf2a160-5c7b-4775-98f7-a431b00c9e5a";
|
||||
const five = "def0f758-b10b-4bc6-a4f4-6461cd5cdcfb"
|
||||
|
||||
const owned = [one, two, three]
|
||||
describe('#PrivilegeChange', () => {
|
||||
|
||||
|
||||
it('should create', () => {
|
||||
const changer = new PrivilegeChange(owned);
|
||||
});
|
||||
it("Should add need privilege id", () => {
|
||||
const changer = new PrivilegeChange(owned);
|
||||
changer.add(four);
|
||||
expect(changer.get().add).toEqual([four]);
|
||||
});
|
||||
it("Should not add already owned privilege id", () => {
|
||||
const changer = new PrivilegeChange(owned);
|
||||
changer.add(one);
|
||||
expect(changer.get().add).toEqual([]);
|
||||
});
|
||||
it("Should delete owned privilege", () => {
|
||||
const changer = new PrivilegeChange(owned);
|
||||
changer.delete(one);
|
||||
expect(changer.get().delete).toEqual([one]);
|
||||
});
|
||||
it("Should not delete not owned privilege id", () => {
|
||||
const changer = new PrivilegeChange(owned);
|
||||
changer.delete(four);
|
||||
expect(changer.get().delete).toEqual([]);
|
||||
})
|
||||
it("Should reset already added id", () => {
|
||||
const changer = new PrivilegeChange(owned);
|
||||
changer.add(four);
|
||||
expect(changer.get().add).toEqual([four]);
|
||||
expect(changer.get().delete).toEqual([]);
|
||||
changer.delete(four);
|
||||
expect(changer.get().add).toEqual([]);
|
||||
expect(changer.get().delete).toEqual([]);
|
||||
});
|
||||
it("Should reset already deleted id", () => {
|
||||
const changer = new PrivilegeChange(owned);
|
||||
changer.delete(one);
|
||||
expect(changer.get().delete).toEqual([one]);
|
||||
expect(changer.get().add).toEqual([]);
|
||||
changer.add(one);
|
||||
expect(changer.get().delete).toEqual([]);
|
||||
expect(changer.get().add).toEqual([]);
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,44 @@
|
||||
import {IPrivilegesChange} from "@components/role-management/role-detail/privilege/IPrivilegesChange";
|
||||
|
||||
export class PrivilegeChange {
|
||||
|
||||
private change= {add: new Set<string>(), delete: new Set<string>()};
|
||||
|
||||
constructor(public ownedPrivileges: string[]) {
|
||||
|
||||
}
|
||||
|
||||
public add(id: string) {
|
||||
|
||||
if (this.change.delete.has(id)) {
|
||||
this.change.delete.delete(id);
|
||||
}
|
||||
if (this.ownedPrivileges.includes(id)) {
|
||||
return;
|
||||
}
|
||||
this.change.add.add(id);
|
||||
}
|
||||
|
||||
public delete(id: string) {
|
||||
if (this.change.add.has(id)) {
|
||||
this.change.add.delete(id);
|
||||
}
|
||||
if (!this.ownedPrivileges.includes(id)) {
|
||||
return;
|
||||
}
|
||||
this.change.delete.add(id);
|
||||
}
|
||||
|
||||
|
||||
isAdded(id: string): boolean {
|
||||
return this.change.add.has(id);
|
||||
}
|
||||
|
||||
isDeleted(id: string) {
|
||||
return this.change.delete.has(id);
|
||||
}
|
||||
|
||||
get(): IPrivilegesChange {
|
||||
return {add: Array.from(this.change.add), delete: Array.from(this.change.delete)};
|
||||
}
|
||||
}
|
@ -8,27 +8,27 @@
|
||||
[routerLink]="['/controller', controller.id, 'management', 'roles']">
|
||||
<mat-icon aria-label="Back to role management">keyboard_arrow_left</mat-icon>
|
||||
</a>
|
||||
<h1 class="col">Role {{role.name}} details</h1>
|
||||
<h1 class="col">Role {{($role | async)?.name}} details</h1>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="details">
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Role name:</mat-label>
|
||||
<input matInput type="text" [ngModel]="role.name">
|
||||
<input matInput type="text" [ngModel]="($role | async)?.name">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Description:</mat-label>
|
||||
<input matInput type="text" [ngModel]="role.description">
|
||||
<input matInput type="text" [ngModel]="($role | async)?.description">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div>Creation date: {{role.created_at}}</div>
|
||||
<div>Last update Date: {{role.updated_at}}</div>
|
||||
<div>UUID: {{role.role_id}}</div>
|
||||
<div>Creation date: {{($role | async)?.created_at}}</div>
|
||||
<div>Last update Date: {{($role | async )?.updated_at}}</div>
|
||||
<div>UUID: {{($role | async)?.role_id}}</div>
|
||||
<div>
|
||||
<mat-checkbox [checked]="role.is_builtin" disabled>Is build in</mat-checkbox>
|
||||
<mat-checkbox [checked]="($role | async)?.is_builtin" disabled>Is build in</mat-checkbox>
|
||||
</div>
|
||||
<div mat-dialog-actions class="button-div">
|
||||
<button mat-button (click)="onUpdate()" tabindex="2" mat-raised-button color="primary"
|
||||
@ -38,7 +38,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider [vertical]="true"></mat-divider>
|
||||
|
||||
<app-privilege
|
||||
[disable]="($role | async)?.is_builtin"
|
||||
[ownedPrivilegesList]="$ownedPrivilegesId | async"
|
||||
[privileges]="privileges | async"
|
||||
(update)="onPrivilegesUpdate($event)"></app-privilege>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,11 +10,22 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.permissions {
|
||||
.privileges {
|
||||
width: 35vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.privilege {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-left: 10px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.permission {
|
||||
@ -33,7 +44,7 @@
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 20px;
|
||||
|
||||
padding-left: 10px;
|
||||
}
|
||||
.header > div {
|
||||
font-size: 2em;
|
||||
|
@ -14,11 +14,15 @@ import {Component, OnInit} from '@angular/core';
|
||||
import {RoleService} from "@services/role.service";
|
||||
import {ActivatedRoute} from "@angular/router";
|
||||
import {Controller} from "@models/controller";
|
||||
import {ControllerService} from "@services/controller.service";
|
||||
import {Role} from "@models/api/role";
|
||||
import {UntypedFormControl, UntypedFormGroup} from "@angular/forms";
|
||||
import {ToasterService} from "@services/toaster.service";
|
||||
import {HttpErrorResponse} from "@angular/common/http";
|
||||
import {Privilege} from "@models/api/Privilege";
|
||||
import {PrivilegeService} from "@services/privilege.service";
|
||||
import {Observable, ReplaySubject} from "rxjs";
|
||||
import {IPrivilegesChange} from "@components/role-management/role-detail/privilege/IPrivilegesChange";
|
||||
import {map} from "rxjs/operators";
|
||||
|
||||
@Component({
|
||||
selector: 'app-role-detail',
|
||||
@ -27,13 +31,20 @@ import {HttpErrorResponse} from "@angular/common/http";
|
||||
})
|
||||
export class RoleDetailComponent implements OnInit {
|
||||
controller: Controller;
|
||||
role: Role;
|
||||
$role: ReplaySubject<Role> = new ReplaySubject<Role>(1);
|
||||
editRoleForm: UntypedFormGroup;
|
||||
$ownedPrivilegesId: Observable<string[]> = this.$role.pipe(map((role: Role) => {
|
||||
return role.privileges.map((p: Privilege) => p.privilege_id);
|
||||
}));
|
||||
|
||||
privileges: Observable<Privilege[]>;
|
||||
private roleId: string;
|
||||
|
||||
constructor(private roleService: RoleService,
|
||||
private controllerService: ControllerService,
|
||||
private toastService: ToasterService,
|
||||
private route: ActivatedRoute) {
|
||||
private route: ActivatedRoute,
|
||||
private privilegeService: PrivilegeService,
|
||||
) {
|
||||
|
||||
this.editRoleForm = new UntypedFormGroup({
|
||||
rolename: new UntypedFormControl(),
|
||||
@ -43,19 +54,40 @@ export class RoleDetailComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.data.subscribe((d: { controller: Controller; role: Role }) => {
|
||||
this.controller = d.controller;
|
||||
this.role = d.role;
|
||||
this.controller = d.controller
|
||||
this.roleId = d.role.role_id;
|
||||
this.privileges = this.privilegeService.get(this.controller);
|
||||
this.roleService.getById(this.controller, this.roleId).subscribe((role: Role) => this.$role.next(role))
|
||||
});
|
||||
}
|
||||
|
||||
onUpdate() {
|
||||
this.roleService.update(this.controller, this.role)
|
||||
.subscribe(() => {
|
||||
this.toastService.success(`role: ${this.role.name} was updated`);
|
||||
},
|
||||
(error: HttpErrorResponse) => {
|
||||
this.toastService.error(`${error.message}
|
||||
this.$role.subscribe((role) => {
|
||||
this.roleService.update(this.controller, role)
|
||||
.subscribe(() => {
|
||||
this.toastService.success(`role: ${role.name} was updated`);
|
||||
this.roleService.getById(this.controller, this.roleId).subscribe((role: Role) => this.$role.next(role))
|
||||
},
|
||||
(error: HttpErrorResponse) => {
|
||||
this.toastService.error(`${error.message}
|
||||
${error.error.message}`);
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
onPrivilegesUpdate(privileges: IPrivilegesChange) {
|
||||
const tasks = [];
|
||||
for (const privilege of privileges.add) {
|
||||
tasks.push(this.roleService.setPrivileges(this.controller, this.roleId, privilege));
|
||||
}
|
||||
for (const privilege of privileges.delete) {
|
||||
tasks.push(this.roleService.removePrivileges(this.controller, this.roleId, privilege));
|
||||
}
|
||||
Observable
|
||||
.forkJoin(tasks)
|
||||
.subscribe(() => {
|
||||
this.roleService.getById(this.controller, this.roleId).subscribe((role: Role) => this.$role.next(role))
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
7
src/app/models/api/Privilege.ts
Normal file
7
src/app/models/api/Privilege.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface Privilege {
|
||||
name: string;
|
||||
description: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
privilege_id: string;
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import {Privilege} from "@models/api/Privilege";
|
||||
|
||||
export interface Role {
|
||||
name: string;
|
||||
description: string;
|
||||
@ -5,4 +7,5 @@ export interface Role {
|
||||
updated_at: string;
|
||||
role_id: string;
|
||||
is_builtin: boolean;
|
||||
privileges: Privilege[];
|
||||
}
|
||||
|
17
src/app/services/privilege.service.ts
Normal file
17
src/app/services/privilege.service.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpController} from "@services/http-controller.service";
|
||||
import {Controller} from "@models/controller";
|
||||
import {Privilege} from "@models/api/Privilege";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PrivilegeService {
|
||||
|
||||
constructor(private httpController: HttpController) { }
|
||||
|
||||
|
||||
get(controller: Controller) {
|
||||
return this.httpController.get<Privilege[]>(controller, "/access/privileges")
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ import {HttpController} from "./http-controller.service";
|
||||
import {Controller} from "../models/controller";
|
||||
import {Group} from "../models/groups/group";
|
||||
import {Role} from "../models/api/role";
|
||||
import {Privilege} from "@models/api/Privilege";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -42,4 +43,13 @@ export class RoleService {
|
||||
update(controller: Controller, role: Role) {
|
||||
return this.httpController.put(controller, `/access/roles/${role.role_id}`, {name: role.name, description: role.description});
|
||||
}
|
||||
|
||||
setPrivileges(controller: Controller, roleId: string, privilegeId: string) {
|
||||
return this.httpController.put(controller, `/access/roles/${roleId}/privileges/${privilegeId}`, undefined);
|
||||
}
|
||||
|
||||
removePrivileges(controller: Controller, roleId: string, privilegeId: string) {
|
||||
return this.httpController.delete(controller, `/access/roles/${roleId}/privileges/${privilegeId}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user