Methods
|
addNewAccount
|
addNewAccount()
|
|
|
|
|
|
Readonly
dataSource
|
Type : unknown
|
Default value : new MatTableDataSource<UserAccount>()
|
|
|
|
|
|
displayedColumns
|
Type : string[]
|
Default value : [
"email",
"userEntityId",
"enabled",
"emailVerified",
"roles",
]
|
|
|
|
hasUserManagementRole
|
Type : unknown
|
Default value : this.sessionInfo.value?.roles.includes(
UserAdminService.ACCOUNT_MANAGER_ROLE,
)
|
|
|
|
users
|
Type : unknown
|
Default value : signal<UserAccount[]>([])
|
|
|
import {
ChangeDetectionStrategy,
Component,
effect,
inject,
OnInit,
signal,
} from "@angular/core";
import { UserAdminService } from "../user-admin-service/user-admin.service";
import { MatDialog } from "@angular/material/dialog";
import { SessionSubject } from "../../session/auth/session-info";
import { UserAccount } from "../user-admin-service/user-account";
import { Logging } from "../../logging/logging.service";
import {
UserDetailsAction,
UserDetailsComponent,
UserDetailsDialogData,
} from "../user-details/user-details.component";
import { ListPaginatorComponent } from "../../common-components/entities-table/list-paginator/list-paginator.component";
import { ViewTitleComponent } from "../../common-components/view-title/view-title.component";
import { MatTableDataSource, MatTableModule } from "@angular/material/table";
import { MatButtonModule } from "@angular/material/button";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { EntityBlockComponent } from "../../basic-datatypes/entity/entity-block/entity-block.component";
import { AlertService } from "../../alerts/alert.service";
import { MatTooltipModule } from "@angular/material/tooltip";
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "app-user-list",
imports: [
ViewTitleComponent,
MatTableModule,
MatButtonModule,
FaIconComponent,
EntityBlockComponent,
ListPaginatorComponent,
MatTooltipModule,
],
templateUrl: "./user-list.component.html",
styleUrl: "./user-list.component.scss",
})
export class UserListComponent implements OnInit {
private readonly userAdminService = inject(UserAdminService);
private readonly dialog = inject(MatDialog);
private readonly sessionInfo = inject(SessionSubject);
private readonly alertService = inject(AlertService);
hasUserManagementRole = this.sessionInfo.value?.roles.includes(
UserAdminService.ACCOUNT_MANAGER_ROLE,
);
users = signal<UserAccount[]>([]);
/** paging is handled by the data source and {@link ListPaginatorComponent} */
readonly dataSource = new MatTableDataSource<UserAccount>();
displayedColumns: string[] = [
"email",
"userEntityId",
"enabled",
"emailVerified",
"roles",
];
constructor() {
effect(() => (this.dataSource.data = this.users()));
}
ngOnInit() {
const isOnline = typeof navigator !== "undefined" ? navigator.onLine : true;
if (this.hasUserManagementRole && isOnline) {
this.loadUsers();
} else if (!isOnline) {
this.alertService.addInfo(
$localize`User accounts cannot be loaded while offline.`,
);
}
}
loadUsers() {
this.userAdminService.getAllUsers().subscribe({
next: (users) => {
this.users.set(users);
// 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();
},
error: (err) => {
Logging.error("Failed to load users:", err);
this.alertService.addWarning(
$localize`Failed to load users. Please try again later or contact your server administrator.`,
);
},
});
}
getRoleNames(userAccount: UserAccount): string {
return userAccount.roles?.map((r) => r.name).join(", ") || "-";
}
addNewAccount() {
this.openUserDetails(null);
}
openUserDetails(user: UserAccount | null) {
const dialogData: UserDetailsDialogData = {
userAccount: user,
};
const dialogRef = this.dialog.open(UserDetailsComponent, {
data: dialogData,
width: "99%",
});
dialogRef.afterClosed().subscribe((action: UserDetailsAction) => {
if (!action) {
// Dialog was closed without an action (e.g., clicked outside or ESC)
return;
}
switch (action.type) {
case "accountUpdated": {
const updatedUsers = this.users().map((u) =>
u.id === action.data.user.id ? action.data.user : u,
);
this.users.set(updatedUsers);
break;
}
case "accountCreated":
this.loadUsers();
break;
}
});
}
}
<div class="flex-row flex-wrap">
<div class="flex-grow">
<app-view-title i18n>User Management</app-view-title>
</div>
@if (hasUserManagementRole) {
<button
mat-stroked-button
color="accent"
class="standard-add-button"
(click)="addNewAccount()"
>
<fa-icon icon="plus-circle" class="standard-icon-with-text"></fa-icon>
<span i18n>Add New Account</span>
</button>
}
</div>
@if (!hasUserManagementRole) {
<div i18n>
Your account does not have the required permissions to manage user accounts.
</div>
} @else {
<p i18n class="margin-bottom-regular">
This page displays all user accounts registered for your system.
</p>
<div class="table-container">
<table mat-table [dataSource]="dataSource" class="full-width table">
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef i18n>Email</th>
<td mat-cell *matCellDef="let user">{{ user.email || "-" }}</td>
</ng-container>
<ng-container matColumnDef="userEntityId">
<th mat-header-cell *matHeaderCellDef i18n>Profile</th>
<td mat-cell *matCellDef="let user">
@if (user.userEntityId) {
<app-entity-block [entityId]="user.userEntityId"></app-entity-block>
} @else {
<span
class="no-profile-hint"
matTooltip="This account is not linked to a profile record. Permission rules based on the user's profile do not apply to this account."
i18n-matTooltip="user list: tooltip for account without profile"
>
<fa-icon
icon="triangle-exclamation"
class="standard-icon-with-text"
></fa-icon>
<span i18n="user list: account without profile"
>No profile linked</span
>
</span>
}
</td>
</ng-container>
<ng-container matColumnDef="enabled">
<th mat-header-cell *matHeaderCellDef i18n>Enabled</th>
<td mat-cell *matCellDef="let user">
@if (user.enabled) {
<span i18n>Yes</span>
} @else {
<span i18n>No</span>
}
</td>
</ng-container>
<ng-container matColumnDef="emailVerified">
<th mat-header-cell *matHeaderCellDef i18n>Email Verified</th>
<td mat-cell *matCellDef="let user">
@if (user.emailVerified) {
<span i18n>Yes</span>
} @else {
<span i18n>No</span>
}
</td>
</ng-container>
<ng-container matColumnDef="roles">
<th mat-header-cell *matHeaderCellDef i18n>Roles</th>
<td mat-cell *matCellDef="let user">{{ getRoleNames(user) }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="openUserDetails(row)"
class="clickable-row"
></tr>
</table>
<app-list-paginator
class="table-footer"
[dataSource]="dataSource"
[idForSavingPagination]="'user-list'"
></app-list-paginator>
</div>
}
@use "variables/colors";
@use "mixins/list-table";
@include list-table.container-and-table;
@include list-table.footer;
@include list-table.clickable-row;
:host ::ng-deep .mat-mdc-paginator .mat-mdc-paginator-container {
justify-content: flex-start;
}
.no-profile-hint {
color: colors.$warn;
}
Legend
Html element with directive