* under the License.
*/
+import {Permission} from "@app/model/permission";
+
export class Role {
id: string
name: string
model_id:string
resource:string
+ child_role_ids: Array<string>
+ parent_role_ids: Array<string>
children: Array<Role>
+ parents: Array<Role>
+ permissions: Array<Permission>
// Web Internal attributes
enabled: boolean = true
children: [
{path: 'list', component: ManageRolesListComponent},
{path: 'edit/:roleid', component: ManageRolesEditComponent},
+ {path: 'edit', component: ManageRolesEditComponent},
{path: '', redirectTo: 'list', pathMatch: 'full'}
]
}
~ under the License.
-->
-<p>manage-roles-edit works!</p>
+<form class="mt-3 mb-3" [formGroup]="userForm" (ngSubmit)="onSubmit()">
+ <div class="form-group row col-md-8">
+ <div class="col-md-1" *ngIf="editRole && !editRole.permanent">{{'form.edit' |translate}} <span
+ class="fas fa-edit"></span></div>
+ <div class="col-md-6" *ngIf="editRole && !editRole.permanent">
+ <input class="form-check-input" type="checkbox" [value]="editMode" [checked]="editMode"
+ (change)="editMode=!editMode"
+ >
+ </div>
+ </div>
+ <div class="form-group row col-md-8">
+ <label class="col-md-2 col-form-label" for="id">{{'roles.attributes.id' |translate}}</label>
+ <div class="col-md-6">
+ <input type="text" formControlName="id" id="id"
+ [ngClass]="valid('id')"
+ [attr.readonly]="true">
+ </div>
+ </div>
+ <div class="form-group row col-md-8">
+ <label class="col-md-2 col-form-label" for="id">{{'roles.attributes.name' |translate}}</label>
+ <div class="col-md-6">
+ <input type="text" formControlName="name" id="name"
+ [ngClass]="valid('name')"
+ [attr.readonly]="editMode?null:'true'">
+ </div>
+ </div>
+ <div class="form-group row col-md-8">
+ <label class="col-md-2 col-form-label" for="id">{{'roles.attributes.description' |translate}}</label>
+ <div class="col-md-6">
+ <input type="text" formControlName="description" id="description"
+ [ngClass]="valid('description')"
+ [attr.readonly]="editMode?null:'true'">
+ </div>
+ </div>
+ <div class="form-group row col-md-8">
+ <label class="col-md-2 col-form-label" for="id">{{'roles.attributes.resource' |translate}}</label>
+ <div class="col-md-6">
+ <input type="text" formControlName="resource" id="resource"
+ [ngClass]="valid('resource')"
+ [attr.readonly]="true">
+ </div>
+ </div>
+ <div class="form-group row col-md-8">
+ <div class="col-md-2"></div>
+ <div class="col-md-6">
+ <div class="form-check">
+ <input class="form-check-input" type="checkbox" formControlName="assignable"
+ id="assignable" [attr.disabled]="true">
+ <label class="form-check-label " for="assignable">
+ {{'roles.attributes.assignable'|translate}}
+ </label>
+ </div>
+ <div class="form-check">
+ <input class="form-check-input" type="checkbox" formControlName="template_instance"
+ id="template_instance" [attr.disabled]="true">
+ <label class="form-check-label " for="template_instance">
+ {{'roles.attributes.template_instance'|translate}}
+ </label>
+ </div>
+ </div>
+ </div>
+</form>
+<hr/>
+<ngb-accordion activeIds="parents,children,permissions">
+ <ngb-panel id="parents" >
+ <ng-template ngbPanelHeader let-opened="opened">
+ <div class="d-flex align-items-center justify-content-between">
+ <button ngbPanelToggle class="btn btn-link text-left shadow-none"><h3>{{'roles.edit.parents'|translate}}</h3></button>
+ <ng-container *ngIf="!opened"><i class="fa fa-eye-slash"></i></ng-container>
+ <ng-container *ngIf="opened"><i class="fa fa-eye"></i></ng-container>
+ </div>
+ </ng-template>
+ <ng-template ngbPanelContent>
+ <ng-container *ngIf="editRole?.parents.length>0">
+ <ul class="list-group" *ngFor="let parentRole of editRole?.parents">
+ <li class="list-group-item"><a routerLink="../{{parentRole?.id}}">{{parentRole?.name}}</a></li>
+ </ul>
+ </ng-container>
+ </ng-template>
+ </ngb-panel>
+ <ngb-panel id="children">
+ <ng-template ngbPanelHeader let-opened="opened">
+ <div class="d-flex align-items-center justify-content-between">
+ <button ngbPanelToggle class="btn btn-link text-left shadow-none"><h3>{{'roles.edit.children'|translate}}</h3></button>
+ <ng-container *ngIf="!opened"><i class="fa fa-eye-slash"></i></ng-container>
+ <ng-container *ngIf="opened"><i class="fa fa-eye"></i></ng-container>
+ </div>
+ </ng-template>
+ <ng-template ngbPanelContent>
+ <ng-container *ngIf="editRole?.children.length>0">
+ <ul class="list-group" *ngFor="let childRole of editRole?.children">
+ <li class="list-group-item"><a routerLink="../{{childRole?.id}}">{{childRole?.name}}</a></li>
+ </ul>
+ </ng-container>
+ </ng-template>
+ </ngb-panel>
+
+ <ngb-panel id="permissions">
+ <ng-template ngbPanelHeader let-opened="opened">
+ <div class="d-flex align-items-center justify-content-between">
+
+ <button ngbPanelToggle class="btn btn-link text-left shadow-none"><h3>{{'roles.edit.permissions'|translate}}</h3></button>
+ <ng-container *ngIf="!opened"><i class="fa fa-eye-slash"></i></ng-container>
+ <ng-container *ngIf="opened"><i class="fa fa-eye"></i></ng-container>
+ </div>
+ </ng-template>
+ <ng-template ngbPanelContent>
+ <ng-container *ngIf="editRole">
+ <table class="table">
+ <thead>
+ <tr>
+ <th>{{'permissions.attributes.permission'|translate}}</th>
+ <th>{{'permissions.attributes.operation'|translate}}</th>
+ <th>{{'permissions.attributes.resource'|translate}}</th>
+ </tr>
+ </thead>
+ <tbody *ngFor="let perm of editRole.permissions">
+ <tr>
+ <td>{{perm.name}}</td>
+ <td>{{perm.operation.name}}</td>
+ <td>{{perm.resource.identifier}}</td>
+ </tr>
+ </tbody>
+
+ </table>
+ </ng-container>
+ </ng-template>
+ </ngb-panel>
+
+ <ngb-panel>
+ <ng-template ngbPanelHeader let-opened="opened">
+ <div class="d-flex align-items-center justify-content-between">
+ <button ngbPanelToggle class="btn btn-link text-left shadow-none"><h3>{{'roles.edit.users'|translate}}</h3></button>
+ <ng-container *ngIf="!opened"><i class="fa fa-eye-slash"></i></ng-container>
+ <ng-container *ngIf="opened"><i class="fa fa-eye"></i></ng-container>
+ </div>
+ </ng-template>
+ <ng-template ngbPanelContent >
+ <h3>There are the users</h3>
+ </ng-template>
+ </ngb-panel>
+
+</ngb-accordion>
* under the License.
*/
-import { Component, OnInit } from '@angular/core';
+import {Component, EventEmitter, OnInit, Output} from '@angular/core';
+import {ActivatedRoute} from "@angular/router";
+import {FormBuilder, Validators} from "@angular/forms";
+import {RoleService} from "@app/services/role.service";
+import {catchError, filter, map, switchMap, tap} from "rxjs/operators";
+import {Role} from '@app/model/role';
+import {ErrorResult} from "@app/model/error-result";
+import {EditBaseComponent} from "@app/modules/shared/edit-base.component";
+import {forkJoin, iif, Observable, of, pipe, zip} from 'rxjs';
@Component({
- selector: 'app-manage-roles-edit',
- templateUrl: './manage-roles-edit.component.html',
- styleUrls: ['./manage-roles-edit.component.scss']
+ selector: 'app-manage-roles-edit',
+ templateUrl: './manage-roles-edit.component.html',
+ styleUrls: ['./manage-roles-edit.component.scss']
})
-export class ManageRolesEditComponent implements OnInit {
+export class ManageRolesEditComponent extends EditBaseComponent<Role> implements OnInit {
- constructor() { }
+ parentsOpened: boolean
- ngOnInit(): void {
- }
+ editRole: Role;
+ editProperties = ['id', 'name', 'description', 'template_instance', 'resource', 'assignable'];
+ originRole;
+ roleCache: Map<string, Role> = new Map<string, Role>();
+
+
+ @Output()
+ roleIdEvent: EventEmitter<string> = new EventEmitter<string>(true);
+
+ constructor(private route: ActivatedRoute, private roleService: RoleService, public fb: FormBuilder) {
+ super(fb);
+ super.init(fb.group({
+ id: ['', [Validators.required]],
+ name: ['', Validators.required],
+ description: [''],
+ resource: [''],
+ template_instance: [''],
+ assignable: ['']
+ }, {}));
+ }
+
+ createEntity(): Role {
+ return new Role();
+ }
+
+ ngOnInit(): void {
+ this.route.params.pipe(
+ map(params => params.roleid),
+ filter(roleid => roleid != null),
+ tap(roleid => {
+ this.roleIdEvent.emit(roleid)
+ }),
+ switchMap((roleid: string) => this.roleService.getRole(roleid)),
+ switchMap((role: Role) => zip(of(role),
+ this.retrieveChildren(role),
+ this.retrieveParents(role))),
+ map((ra: [Role, Role[], Role[]]) => this.combine(ra))
+ ).subscribe(role => {
+ this.editRole = role;
+ this.originRole = role;
+ this.copyToForm(this.editProperties, this.editRole);
+ }, error => {
+ this.editRole = new Role();
+ });
+ }
+
+ /**
+ * Array of [role, children[], parents[]]
+ */
+ combine(roleArray: [Role, Role[], Role[]]): Role {
+ roleArray[0].children = roleArray[1];
+ roleArray[0].parents = roleArray[2];
+ return roleArray[0];
+ }
+
+ private createRole(id: string): Role {
+ let role = new Role();
+ role.id = id;
+ role.name=''
+ return role;
+ }
+
+ getCachedRole(id : string) : Observable<Role> {
+ return of(id).pipe(
+ switchMap(( myId : string ) => {
+ if (this.roleCache.has(myId)) {
+ return of(this.roleCache.get(myId));
+ } else {
+ return this.roleService.getRole(myId).pipe(tap(role => {
+ this.roleCache.set(role.id, role);
+ }),catchError(() => of(this.createRole(id))));
+ }
+ }));
+ }
+
+ retrieveChildren(role: Role): Observable<Role[]> {
+ // ForkJoin does not emit, if one of the observables is failing to emit a object
+ // -> we use catchError()
+ let children: Array<Observable<Role>> = []
+ for (let child_id of role.child_role_ids) {
+ children.push(this.getCachedRole(child_id));
+ }
+ if (children.length>0) {
+ return forkJoin(children);
+ } else {
+ return of([]);
+ }
+ }
+
+ retrieveParents(role: Role): Observable<Role[]> {
+ let parents: Array<Observable<Role>> = []
+ for (let parent_id of role.parent_role_ids) {
+ parents.push(this.getCachedRole(parent_id));
+ }
+ if (parents.length>0) {
+ return forkJoin(parents);
+ } else {
+ return of([]);
+ }
+ }
+
+ onSubmit() {
+ let role = this.copyFromForm(this.editProperties);
+ this.roleService.updateRole(role).pipe(
+ catchError((err: ErrorResult) => {
+ this.error = true;
+ this.success = false;
+ this.errorResult = err;
+ return [];
+ })
+ ).subscribe(roleInfo => {
+ this.error = false;
+ this.success = true;
+ this.errorResult = null;
+ this.result = roleInfo;
+ this.editMode = false;
+ });
+
+ }
}
+
<app-th-sorted [fieldArray]="['id']" contentText="roles.attributes.id"></app-th-sorted>
<app-th-sorted [fieldArray]="['name']" contentText="roles.attributes.name" ></app-th-sorted>
<app-th-sorted [fieldArray]="['description']" contentText="roles.attributes.description" ></app-th-sorted>
+ <app-th-sorted [fieldArray]="['assignable']" contentText="roles.attributes.assignable"></app-th-sorted>
<app-th-sorted [fieldArray]="['template_instance']" contentText="roles.attributes.template_instance" ></app-th-sorted>
<app-th-sorted [fieldArray]="['resource']" contentText="roles.attributes.resource" ></app-th-sorted>
<th>Action</th>
<td><span data-toggle="tooltip" placement="left" ngbTooltip="{{role.id}}">{{role.id}}</span></td>
<td>{{role.name}}</td>
<td>{{role.description}}</td>
+ <td><span class="far" [attr.aria-valuetext]="role.assignable"
+ [ngClass]="role.assignable?'fa-check-circle':'fa-circle'"></span></td>
<td><span class="far" [attr.aria-valuetext]="role.template_instance"
[ngClass]="role.template_instance?'fa-check-circle':'fa-circle'"></span></td>
<td>{{role.resource}}</td>
import {PagedResult} from "@app/model/paged-result";
import {UserInfo} from "@app/model/user-info";
import {RoleService} from "@app/services/role.service";
+import {SortedTableComponent} from "@app/modules/shared/sorted-table-component";
@Component({
selector: 'app-manage-roles-list',
templateUrl: './manage-roles-list.component.html',
styleUrls: ['./manage-roles-list.component.scss']
})
-export class ManageRolesListComponent implements OnInit {
+export class ManageRolesListComponent extends SortedTableComponent<Role> implements OnInit {
- service: EntityService<Role>
-
- constructor(private translator: TranslateService, private roleService : RoleService) {
- this.service = function (searchTerm: string, offset: number, limit: number, orderBy: string[], order: string) : Observable<PagedResult<Role>> {
+ constructor(translator: TranslateService, roleService : RoleService) {
+ super(translator, function (searchTerm: string, offset: number, limit: number, orderBy: string[], order: string): Observable<PagedResult<Role>> {
console.log("Retrieving data " + searchTerm + "," + offset + "," + limit + "," + orderBy + "," + order);
return roleService.query(searchTerm, offset, limit, orderBy, order);
- }
+ });
}
ngOnInit(): void {
// console.log("Activating "+componentReference+" - "+JSON.stringify(componentReference,getCircularReplacer()))
if (componentReference.roleIdEvent!=null) {
let componentEmit : Observable<string> = componentReference.roleIdEvent.pipe(
- tap(userid=>console.log("Event "+componentReference.class+" "+userid)),
map((userid: string) => this.getSubPath(userid)));
if (this.roleId$!=null) {
this.roleId$ = merge(this.roleId$, componentEmit)
<form class="mt-3 mb-3" [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div class="form-group row col-md-8" *ngIf="!editUser.permanent">
- <div class="col-md-1">Edit <span class="fas fa-edit"></span></div>
+ <div class="col-md-1">{{'form.edit' |translate}} <span class="fas fa-edit"></span></div>
<div class="col-md-6">
<input class="form-check-input" type="checkbox" [value]="editMode" [checked]="editMode"
(change)="editMode=!editMode"
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {AbstractControl, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn} from "@angular/forms";
+import {ErrorResult} from '@app/model/error-result';
+
+export abstract class EditBaseComponent<T> {
+
+ editProperties = ['id'];
+ success: boolean = false;
+ error: boolean = false;
+ errorResult: ErrorResult;
+ result: T;
+ formInitialValues;
+ public userForm : FormGroup;
+ public editMode: boolean;
+
+ constructor(public fb: FormBuilder) {
+
+ }
+
+ init(userForm: FormGroup) : void {
+ this.userForm=userForm;
+ this.formInitialValues = userForm.value;
+ }
+
+ abstract createEntity() : T;
+ abstract onSubmit();
+
+ public copyFromForm(properties: string[]): T {
+ let entity: any = this.createEntity();
+ for (let prop of properties) {
+ entity[prop] = this.userForm.get(prop).value;
+ }
+ return entity;
+ }
+
+ public copyToForm(properties: string[], user: T): void {
+ let propMap = {};
+ for (let prop of properties) {
+ let propValue = user[prop] == null ? '' : user[prop];
+ propMap[prop] = propValue;
+ }
+ this.userForm.patchValue(propMap);
+ }
+
+
+ valid(field: string): string[] {
+ if (this.editMode) {
+ let classArr = this.isValid(field);
+ return classArr.concat('form-control')
+ } else {
+ return ['form-control-plaintext'];
+ }
+ }
+
+ isValid(field: string): string[] {
+ let formField = this.userForm.get(field);
+ if (formField.dirty || formField.touched) {
+ if (formField.valid) {
+ return ['is-valid']
+ } else {
+ return ['is-invalid']
+ }
+ } else {
+ return ['']
+ }
+ }
+
+ forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
+ return (control: AbstractControl): { [key: string]: any } | null => {
+ const forbidden = nameRe.test(control.value);
+ return forbidden ? {forbiddenName: {value: control.value}} : null;
+ };
+ }
+
+ getAllErrors(formGroup: FormGroup, errors: string[] = []) : string[] {
+ Object.keys(formGroup.controls).forEach(field => {
+ const control = formGroup.get(field);
+ if (control instanceof FormControl && control.errors != null) {
+ let keys = Object.keys(control.errors).map(errorKey=>field+'.'+errorKey);
+ errors = errors.concat(keys);
+ } else if (control instanceof FormGroup) {
+ errors = errors.concat(this.getAllErrors(control));
+ }
+ });
+ return errors;
+ }
+
+ getAttributeErrors(control:string):string[] {
+ return Object.keys(this.userForm.get(control).errors);
+ }
+}
+
+export function whitespaceValidator(): ValidatorFn {
+ return (control: AbstractControl): ValidationErrors | null => {
+ const hasWhitespace = /\s/g.test(control.value);
+ return hasWhitespace ? {containsWhitespace: {value: control.value}} : null;
+ };
+}
+export function MustMatch(controlName: string, matchingControlName: string) : ValidatorFn {
+ return (formGroup: FormGroup): ValidationErrors | null => {
+ const control = formGroup.controls[controlName];
+ const matchingControl = formGroup.controls[matchingControlName];
+
+ if (matchingControl.errors && !matchingControl.errors.mustMatch) {
+ // return if another validator has already found an error on the matchingControl
+ return;
+ }
+
+ // set error on matchingControl if validation fails
+ if (control.value !== matchingControl.value) {
+ matchingControl.setErrors({mustMatch: true});
+ } else {
+ matchingControl.setErrors(null);
+ }
+ }
+}
\ No newline at end of file
import {PaginatedEntitiesComponent} from "./paginated-entities/paginated-entities.component";
import {SortedTableHeaderComponent} from "./sorted-table-header/sorted-table-header.component";
import {SortedTableHeaderRowComponent} from "./sorted-table-header-row/sorted-table-header-row.component";
-import {NgbPaginationModule, NgbTooltipModule} from "@ng-bootstrap/ng-bootstrap";
+import {NgbAccordionModule, NgbPaginationModule, NgbTooltipModule} from "@ng-bootstrap/ng-bootstrap";
import {TranslateCompiler, TranslateLoader, TranslateModule} from "@ngx-translate/core";
import {TranslateMessageFormatCompiler} from "ngx-translate-messageformat-compiler";
import {HttpClient} from "@angular/common/http";
import {TranslateHttpLoader} from "@ngx-translate/http-loader";
import {RouterModule} from "@angular/router";
+import {SortedTableComponent} from "@app/modules/shared/sorted-table-component";
@NgModule({
TranslateModule,
NgbPaginationModule,
NgbTooltipModule,
+ NgbAccordionModule,
PaginatedEntitiesComponent,
SortedTableHeaderComponent,
SortedTableHeaderRowComponent
--- /dev/null
+import {EntityService} from "@app/model/entity-service";
+import {TranslateService} from "@ngx-translate/core";
+
+export class SortedTableComponent<T> {
+
+ sortField = ["id"];
+ sortOrder = "asc";
+
+ constructor(public translator : TranslateService, public service: EntityService<T>) {
+ }
+}
}
public query(searchTerm: string, offset: number = 0, limit: number = 10, orderBy: string[] = ['id'], order: string = 'asc'): Observable<PagedResult<Role>> {
- console.log("getRoleList " + searchTerm + "," + offset + "," + limit + "," + orderBy + "," + order);
if (searchTerm == null) {
searchTerm = ""
}
});
}
+ public getRole(roleId:string) : Observable<Role> {
+ return this.rest.executeRestCall("get", "redback", "roles/" + roleId, null);
+ }
+
+ public updateRole(role:Role) : Observable<Role> {
+ return this.rest.executeRestCall("put", "redback", "roles/" + role.id, role);
+ }
+
}
"head": "Roles List"
},
"edit": {
- "head": "Edit/View Role"
+ "head": "Edit/View Role",
+ "parents": "Parents",
+ "children": "Children",
+ "permissions": "Permissions",
+ "users": "Users"
},
"attributes": {
"id": "Identifier",
"name": "Name",
"description": "Description",
- "template_instance": "Template Instance"
+ "template_instance": "Template Instance",
+ "resource": "Repository",
+ "assignable": "Assignable"
}
},
+ "permissions": {
+ "attributes": {
+ "permission": "Permission",
+ "operation": "Operation",
+ "resource": "Resource"
+ }
+ },
"search": {
"button": "Search",
"label": "Enter your search term",
"yes": "Yes",
"no": "No",
"save": "Save Changes"
- }
+ },
+ "edit": "Edit"
},
"password": {
"violations" : {