Merge branch 'RED-3804' - Configure entity permissions
This commit is contained in:
commit
6ed84d9ca0
@ -125,6 +125,9 @@ const components = [AppComponent, AuthErrorComponent, NotificationsComponent, Sp
|
||||
PDF: {
|
||||
enabled: false,
|
||||
},
|
||||
STATS: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
} as ILoggerConfig,
|
||||
},
|
||||
|
||||
16
apps/red-ui/src/app/guards/permissions-guard.ts
Normal file
16
apps/red-ui/src/app/guards/permissions-guard.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate } from '@angular/router';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { EntityPermissionsService } from '../services/entity-permissions/entity-permissions.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PermissionsGuard implements CanActivate {
|
||||
constructor(private readonly _entityPermissionsService: EntityPermissionsService) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
|
||||
const targetObject = route.data.permissionsObject;
|
||||
await firstValueFrom(this._entityPermissionsService.loadConfigFor(targetObject));
|
||||
await firstValueFrom(this._entityPermissionsService.loadFor(targetObject));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,7 @@ import { DossierStatesListingScreenComponent } from './screens/dossier-states-li
|
||||
import { DossiersGuard } from '@guards/dossiers.guard';
|
||||
import { ACTIVE_DOSSIERS_SERVICE } from '../../tokens';
|
||||
import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-screen.component';
|
||||
import { PermissionsGuard } from '../../guards/permissions-guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: 'dossier-templates', pathMatch: 'full' },
|
||||
@ -159,6 +160,16 @@ const routes: Routes = [
|
||||
requiredRoles: ['RED_USER_ADMIN'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'dossier-permissions',
|
||||
component: BaseAdminScreenComponent,
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [AuthGuard, RedRoleGuard, PermissionsGuard],
|
||||
permissionsObject: 'Dossier',
|
||||
},
|
||||
loadChildren: () => import('./screens/permissions/permissions.module').then(m => m.PermissionsModule),
|
||||
},
|
||||
{
|
||||
path: 'license-info',
|
||||
component: LicenseInformationScreenComponent,
|
||||
|
||||
@ -44,6 +44,7 @@ export class AdminSideNavComponent implements OnInit {
|
||||
},
|
||||
{ screen: 'audit', label: _('admin-side-nav.audit'), hideIf: !this.currentUser.isAdmin },
|
||||
{ screen: 'users', label: _('admin-side-nav.user-management'), hideIf: !this.currentUser.isUserAdmin },
|
||||
{ screen: 'dossier-permissions', label: _('dossier-permissions'), hideIf: !this.currentUser.isAdmin },
|
||||
{
|
||||
screen: 'general-config',
|
||||
label: _('admin-side-nav.configurations'),
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TableColumnConfig } from '@iqser/common-ui';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { PermissionsMapping } from '@red/domain';
|
||||
import { PermissionsConfigurationMapService } from '@services/entity-permissions/permissions-configuration-map.service';
|
||||
import { permissionsTranslations } from '../../translations/permissions-translations';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
constructor(private readonly _permissionsConfigurationMapService: PermissionsConfigurationMapService) {}
|
||||
|
||||
tableConfig(targetObject: string): TableColumnConfig<PermissionsMapping>[] {
|
||||
const columns = this._permissionsConfigurationMapService.getMappedPermissions(targetObject).map(p => ({
|
||||
label: permissionsTranslations.mapped[p],
|
||||
class: 'flex-center',
|
||||
}));
|
||||
|
||||
return [{ label: _('permissions-screen.table-col-names.permission') }, ...columns];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
<iqser-page-header
|
||||
(closeAction)="routerHistoryService.navigateToLastDossiersScreen()"
|
||||
[pageLabel]="'permissions-screen.label' | translate: { targetObject: this.targetObject }"
|
||||
[showCloseButton]="currentUser.isUser"
|
||||
></iqser-page-header>
|
||||
|
||||
<iqser-table [itemSize]="80" [tableColumnConfigs]="tableColumnConfigs"></iqser-table>
|
||||
|
||||
<ng-template #tableItemTemplate let-entity="entity">
|
||||
<div *ngIf="cast(entity) as config">
|
||||
<div class="cell">{{ translations[targetObject][config.searchKey] | translate }}</div>
|
||||
<div *ngFor="let permission of mappedPermissions" class="center cell">
|
||||
<mat-slide-toggle
|
||||
(toggleChange)="togglePermission(config.searchKey, permission)"
|
||||
[checked]="config.getValue(permission)"
|
||||
color="primary"
|
||||
></mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
@ -0,0 +1,4 @@
|
||||
:host {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
import { ChangeDetectionStrategy, Component, forwardRef, Injector } from '@angular/core';
|
||||
import { DefaultListingServices, ListingComponent, LoadingService, SortingOrders, TableColumnConfig } from '@iqser/common-ui';
|
||||
import { PermissionsMapping } from '@red/domain';
|
||||
import { ConfigService } from '../config.service';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { EntityPermissionsService } from '@services/entity-permissions/entity-permissions.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { PermissionsMapService } from '@services/entity-permissions/permissions-map.service';
|
||||
import { PermissionsConfigurationMapService } from '@services/entity-permissions/permissions-configuration-map.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { permissionsTranslations } from '../../../translations/permissions-translations';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
templateUrl: './permissions-screen.component.html',
|
||||
styleUrls: ['./permissions-screen.component.scss'],
|
||||
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => PermissionsScreenComponent) }],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PermissionsScreenComponent extends ListingComponent<PermissionsMapping> {
|
||||
readonly currentUser = this._userService.currentUser;
|
||||
readonly translations = permissionsTranslations;
|
||||
readonly tableColumnConfigs: TableColumnConfig<PermissionsMapping>[];
|
||||
readonly tableHeaderLabel = _('permissions-screen.table-header.title');
|
||||
readonly targetObject: string;
|
||||
readonly mappedPermissions: string[];
|
||||
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _configService: ConfigService,
|
||||
private readonly _userService: UserService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
private readonly _entityPermissionsService: EntityPermissionsService,
|
||||
private readonly _permissionsMapService: PermissionsMapService,
|
||||
private readonly _permissionsConfigurationMapService: PermissionsConfigurationMapService,
|
||||
private readonly _route: ActivatedRoute,
|
||||
readonly routerHistoryService: RouterHistoryService,
|
||||
) {
|
||||
super(_injector);
|
||||
this.targetObject = _route.snapshot.data.permissionsObject;
|
||||
this.tableColumnConfigs = this._configService.tableConfig(this.targetObject);
|
||||
this.mappedPermissions = this._permissionsConfigurationMapService.getMappedPermissions(this.targetObject);
|
||||
this.entitiesService.setEntities(this._permissionsMapService.get(this.targetObject));
|
||||
this.sortingService.setSortingOption({
|
||||
column: 'sort',
|
||||
order: SortingOrders.asc,
|
||||
});
|
||||
this._permissionsMapService
|
||||
.get$(this.targetObject)
|
||||
.pipe(tap(permissions => this.entitiesService.setEntities(permissions)))
|
||||
.pipe(untilDestroyed(this))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
async togglePermission(targetPermission: string, changedPermission: string): Promise<void> {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._entityPermissionsService.togglePermission(this.targetObject, targetPermission, changedPermission));
|
||||
this._loadingService.stop();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { SharedModule } from '@shared/shared.module';
|
||||
import { PermissionsScreenComponent } from './permissions-screen/permissions-screen.component';
|
||||
import { ConfigService } from './config.service';
|
||||
import { CommonUiModule } from '@iqser/common-ui';
|
||||
|
||||
const routes = [{ path: '', component: PermissionsScreenComponent }];
|
||||
|
||||
@NgModule({
|
||||
declarations: [PermissionsScreenComponent],
|
||||
imports: [RouterModule.forChild(routes), CommonModule, SharedModule, CommonUiModule],
|
||||
providers: [ConfigService],
|
||||
})
|
||||
export class PermissionsModule {}
|
||||
@ -0,0 +1,14 @@
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
|
||||
export const permissionsTranslations: Record<string, Record<string, string>> = {
|
||||
mapped: {
|
||||
OWNER: _('permissions-screen.mapped.owner'),
|
||||
APPROVE: _('permissions-screen.mapped.approve'),
|
||||
REVIEW: _('permissions-screen.mapped.review'),
|
||||
EVERYONE_ELSE: _('permissions-screen.mapped.everyone-else'),
|
||||
},
|
||||
Dossier: {
|
||||
VIEW_OBJECT: _('permissions-screen.dossier.view'),
|
||||
ACCESS_OBJECT: _('permissions-screen.dossier.access'),
|
||||
},
|
||||
} as const;
|
||||
@ -57,18 +57,18 @@ export class AnnotationDetailsComponent implements OnChanges {
|
||||
[
|
||||
{
|
||||
icon: 'red:dictionary',
|
||||
description: 'annotation-engines.dictionary',
|
||||
description: _('annotation-engines.dictionary'),
|
||||
show: AnnotationDetailsComponent._isBasedOn(annotation, Engines.DICTIONARY),
|
||||
translateParams: { isHint: this.annotation.hint },
|
||||
},
|
||||
{
|
||||
icon: 'red:ai',
|
||||
description: 'annotation-engines.ner',
|
||||
description: _('annotation-engines.ner'),
|
||||
show: AnnotationDetailsComponent._isBasedOn(annotation, Engines.NER),
|
||||
},
|
||||
{
|
||||
icon: 'red:rule',
|
||||
description: 'annotation-engines.rule',
|
||||
description: _('annotation-engines.rule'),
|
||||
show: AnnotationDetailsComponent._isBasedOn(annotation, Engines.RULE),
|
||||
translateParams: { rule: this.annotation.legalBasisValue || '' },
|
||||
},
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { GenericService, mapEach } from '@iqser/common-ui';
|
||||
import { IPermissionsMapping, PermissionsMapping } from '@red/domain';
|
||||
import { Observable } from 'rxjs';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { PermissionsConfigurationMapService } from './permissions-configuration-map.service';
|
||||
import { PermissionsMapService } from './permissions-map.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class EntityPermissionsService extends GenericService<IPermissionsMapping> {
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _permissionsConfigurationMapService: PermissionsConfigurationMapService,
|
||||
private readonly _permissionsMapService: PermissionsMapService,
|
||||
) {
|
||||
super(_injector, 'permissions');
|
||||
}
|
||||
|
||||
loadConfigFor(targetObject: string): Observable<PermissionsMapping[]> {
|
||||
return this._http.get<IPermissionsMapping[]>(`/${this._defaultModelPath}/${targetObject}/mapping`).pipe(
|
||||
mapEach(mapping => new PermissionsMapping(mapping, targetObject)),
|
||||
tap(mappings => this._permissionsConfigurationMapService.set(targetObject, mappings)),
|
||||
);
|
||||
}
|
||||
|
||||
loadFor(targetObject: string): Observable<PermissionsMapping[]> {
|
||||
return this._http.get<IPermissionsMapping[]>(`/${this._defaultModelPath}/${targetObject}`).pipe(
|
||||
mapEach(mapping => new PermissionsMapping(mapping, targetObject)),
|
||||
tap(mappings => this._permissionsMapService.set(targetObject, mappings)),
|
||||
);
|
||||
}
|
||||
|
||||
togglePermission(targetObject: string, targetPermission: string, changedPermission: string): Observable<PermissionsMapping[]> {
|
||||
const config = this._permissionsConfigurationMapService.get(targetObject);
|
||||
const targetPermissionConfig = config.find(p => p.searchKey === targetPermission);
|
||||
const permissions = this._permissionsMapService.get(targetObject);
|
||||
const currentTargetPermissionValues = this._permissionsMapService.get(targetObject, targetPermission);
|
||||
const index = currentTargetPermissionValues.mappedPermissions.findIndex(p => p.name === changedPermission);
|
||||
if (index !== -1) {
|
||||
currentTargetPermissionValues.mappedPermissions.splice(index, 1);
|
||||
} else {
|
||||
const permission = targetPermissionConfig.mappedPermissions.find(p => p.name === changedPermission);
|
||||
currentTargetPermissionValues.mappedPermissions.push(permission);
|
||||
}
|
||||
return this._post(permissions, `${this._defaultModelPath}/${targetObject}`).pipe(switchMap(() => this.loadFor(targetObject)));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { IPermissionsMapping, PermissionsMapping } from '@red/domain';
|
||||
import { EntitiesMapService } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PermissionsConfigurationMapService extends EntitiesMapService<PermissionsMapping, IPermissionsMapping> {
|
||||
constructor() {
|
||||
super('name');
|
||||
}
|
||||
|
||||
getMappedPermissions(targetObject: string): string[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
this.get(targetObject)
|
||||
.flatMap(p => p.mappedPermissions)
|
||||
.map(p => p.name),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { IPermissionsMapping, PermissionsMapping } from '@red/domain';
|
||||
import { EntitiesMapService } from '@iqser/common-ui';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PermissionsMapService extends EntitiesMapService<PermissionsMapping, IPermissionsMapping> {
|
||||
constructor() {
|
||||
super('name');
|
||||
}
|
||||
}
|
||||
@ -864,6 +864,7 @@
|
||||
"under-review": "In Review",
|
||||
"upload-files": "Sie können Dateien überall per Drag and Drop platzieren..."
|
||||
},
|
||||
"dossier-permissions": "",
|
||||
"dossier-states-listing": {
|
||||
"action": {
|
||||
"delete": "",
|
||||
@ -1626,6 +1627,25 @@
|
||||
},
|
||||
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} Kurzinfos für Anmerkungen"
|
||||
},
|
||||
"permissions-screen": {
|
||||
"dossier": {
|
||||
"access": "",
|
||||
"view": ""
|
||||
},
|
||||
"label": "",
|
||||
"mapped": {
|
||||
"approve": "",
|
||||
"everyone-else": "",
|
||||
"owner": "",
|
||||
"review": ""
|
||||
},
|
||||
"table-col-names": {
|
||||
"permission": ""
|
||||
},
|
||||
"table-header": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"processing-status": {
|
||||
"ocr": "",
|
||||
"pending": "",
|
||||
|
||||
@ -864,6 +864,7 @@
|
||||
"under-review": "Under Review",
|
||||
"upload-files": "Drag & drop files anywhere..."
|
||||
},
|
||||
"dossier-permissions": "Dossier Permissions",
|
||||
"dossier-states-listing": {
|
||||
"action": {
|
||||
"delete": "Delete State",
|
||||
@ -1626,6 +1627,25 @@
|
||||
},
|
||||
"toggle-tooltips": "{active, select, true{Disable} false{Enable} other{}} annotation tooltips"
|
||||
},
|
||||
"permissions-screen": {
|
||||
"dossier": {
|
||||
"access": "Access Dossier",
|
||||
"view": "View Dossier"
|
||||
},
|
||||
"label": "{targetObject, select, Dossier{Dossier} other{}} Permissions",
|
||||
"mapped": {
|
||||
"approve": "Approvers",
|
||||
"everyone-else": "Everyone else",
|
||||
"owner": "Owner",
|
||||
"review": "Reviewers"
|
||||
},
|
||||
"table-col-names": {
|
||||
"permission": "Permission"
|
||||
},
|
||||
"table-header": {
|
||||
"title": "{length} {length, plural, one{Permission} other{Permissions}}"
|
||||
}
|
||||
},
|
||||
"processing-status": {
|
||||
"ocr": "OCR",
|
||||
"pending": "Pending",
|
||||
|
||||
@ -23,3 +23,4 @@ export * from './lib/dossier-stats';
|
||||
export * from './lib/dossier-state';
|
||||
export * from './lib/trash';
|
||||
export * from './lib/text-highlight';
|
||||
export * from './lib/permissions';
|
||||
|
||||
3
libs/red-domain/src/lib/permissions/index.ts
Normal file
3
libs/red-domain/src/lib/permissions/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './permissions-mapping';
|
||||
export * from './permissions';
|
||||
export * from './permissions-mapping.model';
|
||||
@ -0,0 +1,32 @@
|
||||
import { Entity } from '@iqser/common-ui';
|
||||
import { IPermissionsMapping } from './permissions-mapping';
|
||||
import { IPermission } from './permissions';
|
||||
|
||||
export class PermissionsMapping extends Entity<IPermissionsMapping> implements IPermissionsMapping {
|
||||
readonly routerLink = undefined;
|
||||
readonly mappedPermissions: IPermission[];
|
||||
readonly targetPermission: IPermission;
|
||||
readonly sort: number;
|
||||
|
||||
readonly #currentValuesMap = new Map<string, boolean>();
|
||||
|
||||
constructor(permissionsMapping: IPermissionsMapping, readonly targetObject: string) {
|
||||
super(permissionsMapping);
|
||||
this.mappedPermissions = permissionsMapping.mappedPermissions;
|
||||
this.targetPermission = permissionsMapping.targetPermission;
|
||||
this.sort = this.targetPermission.sort;
|
||||
this.mappedPermissions.forEach(permission => this.#currentValuesMap.set(permission.name, true));
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this.targetPermission.name;
|
||||
}
|
||||
|
||||
get searchKey(): string {
|
||||
return this.targetPermission.name;
|
||||
}
|
||||
|
||||
getValue(permissionName: string): boolean {
|
||||
return this.#currentValuesMap.has(permissionName);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
import { IPermission } from './permissions';
|
||||
|
||||
export interface IPermissionsMapping {
|
||||
mappedPermissions: IPermission[];
|
||||
targetPermission: IPermission;
|
||||
}
|
||||
5
libs/red-domain/src/lib/permissions/permissions.ts
Normal file
5
libs/red-domain/src/lib/permissions/permissions.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface IPermission {
|
||||
mask: number;
|
||||
name: string;
|
||||
sort: number;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user