src/app/core/admin/admin-role-permissions/roles-list/admin-roles-list.component.ts
Admin overview of all user roles and their permission rules, linking to the details of each role.
| changeDetection | ChangeDetectionStrategy.OnPush |
| selector | app-admin-roles-list |
| standalone | true |
| imports |
MatTableModule
MatButtonModule
MatMenuModule
MatTooltipModule
|
| styleUrls | ./admin-roles-list.component.scss |
| templateUrl | ./admin-roles-list.component.html |
| styleUrl | ./admin-roles-list.component.scss |
No results matching.
ViewTitleComponent
MatTableModule
ListPaginatorComponent
MatButtonModule
MatMenuModule
MatTooltipModule
FaIconComponent
RouterLink
OnInit
Properties |
|
Methods |
|
| Async editJson |
editJson()
|
|
Edit the raw permissions config JSON as a fallback for advanced use cases.
Returns :
any
|
| openRoleDetails | ||||||
openRoleDetails(role: RoleWithPermissions)
|
||||||
|
Parameters :
Returns :
void
|
| Readonly addDisabledTooltip |
Type : unknown
|
Default value : $localize`Your account does not have permission to create roles in the user account server.`
|
| Readonly canManageRoles |
Type : unknown
|
Default value : this.rolePermissionsService.canManageRoles
|
|
whether the user may create/delete roles in the authentication server (reactive) |
| Readonly dataSource |
Type : unknown
|
Default value : new MatTableDataSource<RoleWithPermissions>()
|
|
the roles to display; paging is handled by ListPaginatorComponent |
| Readonly defaultRole |
Type : unknown
|
Default value : DEFAULT_ROLE
|
|
name and description of the "_default" role, which roles without own rules fall back to |
| Readonly displayedColumns |
Type : []
|
Default value : ["name", "description", "permissions"]
|
import {
ChangeDetectionStrategy,
Component,
OnInit,
inject,
} from "@angular/core";
import { MatButtonModule } from "@angular/material/button";
import { MatMenuModule } from "@angular/material/menu";
import { MatTableDataSource, MatTableModule } from "@angular/material/table";
import { MatTooltipModule } from "@angular/material/tooltip";
import { ActivatedRoute, Router, RouterLink } from "@angular/router";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { firstValueFrom } from "rxjs";
import { ListPaginatorComponent } from "../../../common-components/entities-table/list-paginator/list-paginator.component";
import { ViewTitleComponent } from "../../../common-components/view-title/view-title.component";
import { Logging } from "../../../logging/logging.service";
import { JsonEditorService } from "../../json-editor/json-editor.service";
import { DEFAULT_ROLE } from "../../../permissions/reserved-roles";
import {
RolePermissionsService,
RoleWithPermissions,
} from "../role-permissions.service";
/**
* Admin overview of all user roles and their permission rules,
* linking to the details of each role.
*/
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-admin-roles-list",
imports: [
ViewTitleComponent,
MatTableModule,
ListPaginatorComponent,
MatButtonModule,
MatMenuModule,
MatTooltipModule,
FaIconComponent,
RouterLink,
],
templateUrl: "./admin-roles-list.component.html",
styleUrl: "./admin-roles-list.component.scss",
})
export class AdminRolesListComponent implements OnInit {
private readonly rolePermissionsService = inject(RolePermissionsService);
private readonly jsonEditorService = inject(JsonEditorService);
private readonly router = inject(Router);
private readonly route = inject(ActivatedRoute);
/** the roles to display; paging is handled by {@link ListPaginatorComponent} */
readonly dataSource = new MatTableDataSource<RoleWithPermissions>();
readonly displayedColumns = ["name", "description", "permissions"];
/** name and description of the "_default" role, which roles without own rules fall back to */
readonly defaultRole = DEFAULT_ROLE;
/** whether the user may create/delete roles in the authentication server (reactive) */
readonly canManageRoles = this.rolePermissionsService.canManageRoles;
readonly addDisabledTooltip = $localize`Your account does not have permission to create roles in the user account server.`;
ngOnInit() {
this.loadRoles();
}
private async loadRoles() {
try {
this.dataSource.data = await this.rolePermissionsService.loadRoles();
// a freshly loaded list starts at the first page: the data source only
// ever clamps the page index downwards, never resets it
this.dataSource.paginator?.firstPage();
} catch (err) {
Logging.error("Failed to load roles:", err);
}
}
openRoleDetails(role: RoleWithPermissions) {
this.router.navigate([role.name], { relativeTo: this.route });
}
/**
* Edit the raw permissions config JSON as a fallback for advanced use cases.
*/
async editJson() {
const config = await this.rolePermissionsService.loadPermissionsConfig();
const updatedData = await firstValueFrom(
this.jsonEditorService.openJsonEditorDialog(config.data),
);
if (!updatedData) return;
await this.rolePermissionsService.savePermissionsConfig(updatedData);
await this.loadRoles();
}
}
<div class="flex-row flex-wrap">
<div class="flex-grow">
<app-view-title i18n>Roles & Permissions</app-view-title>
</div>
<div class="flex-row gap-small align-center">
<!-- tooltip on the button itself, kept hoverable while disabled via
"disabledInteractive"; the null routerLink keeps the interactive
disabled button from navigating -->
<button
mat-stroked-button
color="accent"
class="standard-add-button"
[routerLink]="canManageRoles() ? 'new' : null"
[disabled]="!canManageRoles()"
[disabledInteractive]="!canManageRoles()"
[matTooltip]="addDisabledTooltip"
[matTooltipDisabled]="canManageRoles()"
[attr.aria-describedby]="
canManageRoles() ? null : 'role-add-disabled-reason'
"
>
<fa-icon icon="plus-circle" class="standard-icon-with-text"></fa-icon>
<span i18n>Add New Role</span>
</button>
@if (!canManageRoles()) {
<span class="visually-hidden" id="role-add-disabled-reason">{{
addDisabledTooltip
}}</span>
}
<button
mat-icon-button
[matMenuTriggerFor]="moreMenu"
i18n-aria-label
aria-label="More options"
>
<fa-icon icon="ellipsis-vertical"></fa-icon>
</button>
<mat-menu #moreMenu="matMenu">
<button mat-menu-item (click)="editJson()">
<fa-icon icon="wrench" class="standard-icon-with-text"></fa-icon>
<span i18n>Edit JSON</span>
</button>
</mat-menu>
</div>
</div>
<p i18n class="margin-bottom-regular">
Roles define what users can see and do in the system. Each user can have one
or several roles assigned in their user account.
</p>
<div class="table-container">
<table mat-table [dataSource]="dataSource" class="full-width table">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef i18n>Role</th>
<td mat-cell *matCellDef="let role">
<span class="flex-row align-center gap-small">
{{ role.name }}
@if (role.isProtected) {
<fa-icon
icon="lock"
class="protected-role-icon text-secondary"
i18n-matTooltip
matTooltip="Protected role, cannot be deleted"
></fa-icon>
}
</span>
</td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell *matHeaderCellDef i18n>Description</th>
<td mat-cell *matCellDef="let role">{{ role.description || "-" }}</td>
</ng-container>
<ng-container matColumnDef="permissions">
<th mat-header-cell *matHeaderCellDef i18n>Permissions</th>
<td mat-cell *matCellDef="let role">
@if (role.rules) {
<span i18n>
{role.rules.length, plural,
one {1 rule}
other {{{ role.rules.length }} rules}
}
</span>
} @else if (role.isVirtual) {
<span class="text-secondary" i18n>No permissions defined</span>
} @else {
<span class="text-secondary" i18n>
No permissions defined, falls back to the "{{ defaultRole.label }}"
role
</span>
}
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
tabindex="0"
(click)="openRoleDetails(row)"
(keydown.enter)="openRoleDetails(row)"
(keydown.space)="$event.preventDefault(); openRoleDetails(row)"
class="clickable-row"
></tr>
</table>
<app-list-paginator
class="table-footer"
[dataSource]="dataSource"
[idForSavingPagination]="'admin-roles-list'"
></app-list-paginator>
</div>
./admin-roles-list.component.scss
@use "mixins/list-table";
// same table styling as the user accounts list
@include list-table.container-and-table;
@include list-table.footer;
@include list-table.clickable-row;
.protected-role-icon {
font-size: 11px;
opacity: 0.7;
}