add user delete button

This commit is contained in:
rubikscraft
2022-03-23 19:29:40 +01:00
parent e1ebcace35
commit 860476ecd4
15 changed files with 212 additions and 43 deletions

View File

@@ -1,5 +1,6 @@
import { FormControl } from '@angular/forms';
import Fuse from 'fuse.js';
import { Permissions } from 'picsur-shared/dist/dto/permissions';
import { PermanentRolesList } from 'picsur-shared/dist/dto/roles.dto';
import { ERole } from 'picsur-shared/dist/entities/role.entity';
import { BehaviorSubject, Subscription } from 'rxjs';
@@ -71,6 +72,23 @@ export class UpdateUserControl {
return true;
}
public getEffectivePermissions(): Permissions {
const permissions: Permissions = [];
for (const role of this.selectedRoles) {
const fullRole = this.fullRoles.find((r) => r.name === role);
if (!fullRole) {
console.warn(`Role ${role} not found`);
continue;
}
permissions.push(
...fullRole.permissions.filter((p) => !permissions.includes(p))
);
}
return permissions;
}
// Data interaction
public putAllRoles(roles: ERole[]) {

View File

@@ -0,0 +1,15 @@
<div class="row">
<div class="col">
<h2>Are you sure you want to delete {{ data.username }}?</h2>
</div>
</div>
<div class="row centered">
<div class="col">
<button mat-raised-button class="button-red mx-2" (click)="onDelete()">
Delete
</button>
<button mat-raised-button color="primary" class="mx-2" (click)="onCancel()">
Cancel
</button>
</div>
</div>

View File

@@ -0,0 +1,3 @@
.button-red {
background-color: #C62828;
}

View File

@@ -0,0 +1,43 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { HasFailed } from 'picsur-shared/dist/types';
import { SnackBarType } from 'src/app/models/snack-bar-type';
import { UserManageService } from 'src/app/services/api/usermanage.service';
import { UtilService } from 'src/app/util/util.service';
@Component({
selector: 'app-delete-confirm-dialog',
templateUrl: './delete-confirm-dialog.component.html',
styleUrls: ['./delete-confirm-dialog.component.scss'],
})
export class DeleteConfirmDialogComponent implements OnInit {
constructor(
public dialogRef: MatDialogRef<DeleteConfirmDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: EUser,
private userManageService: UserManageService,
private utilService: UtilService
) {}
ngOnInit(): void {
console.log('Dialog', this.data);
}
async onDelete() {
const result = await this.userManageService.deleteUser(this.data.username);
if (HasFailed(result)) {
this.utilService.showSnackBar(
'Failed to delete user',
SnackBarType.Error
);
} else {
this.utilService.showSnackBar('User deleted', SnackBarType.Success);
}
this.dialogRef.close();
}
onCancel() {
this.dialogRef.close();
}
}

View File

@@ -24,9 +24,9 @@
name="username"
required
/>
<mat-error *ngIf="model.username.errors">{{
model.usernameError
}}</mat-error>
<mat-error *ngIf="model.username.errors">
{{ model.usernameError }}
</mat-error>
</mat-form-field>
</div>
</div>
@@ -42,9 +42,9 @@
name="password"
[required]="adding"
/>
<mat-error *ngIf="model.password.errors">{{
model.passwordError
}}</mat-error>
<mat-error *ngIf="model.password.errors">
{{ model.passwordError }}
</mat-error>
</mat-form-field>
</div>
</div>
@@ -57,6 +57,7 @@
<mat-chip
*ngFor="let role of model.selectedRoles"
[removable]="model.isRemovable(role)"
[disabled]="!model.isRemovable(role)"
(removed)="removeRole(role)"
>
{{ role }}
@@ -91,7 +92,23 @@
</div>
<div class="row">
<div class="col-12 py-2">
<div class="col-12">
<h3>Effective Permissions</h3>
</div>
<div class="col-12">
<mat-chip-list aria-label="User Permissions">
<mat-chip
*ngFor="let permission of getEffectivePermissions()"
[disableRipple]="true"
>
{{ permission }}
</mat-chip>
</mat-chip-list>
</div>
</div>
<div class="row">
<div class="col-12 py-4">
<button mat-raised-button color="accent" type="submit">
{{ editing ? "Update" : "Add" }}
</button>

View File

@@ -3,6 +3,7 @@ import { Component, OnInit } from '@angular/core';
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatChipInputEvent } from '@angular/material/chips';
import { ActivatedRoute, Router } from '@angular/router';
import { UIFriendlyPermissions } from 'picsur-shared/dist/dto/permissions';
import { HasFailed } from 'picsur-shared/dist/types';
import { UpdateUserControl } from 'src/app/models/forms/updateuser.control';
import { SnackBarType } from 'src/app/models/snack-bar-type';
@@ -77,6 +78,12 @@ export class SettingsUsersEditComponent implements OnInit {
this.model.putAllRoles(roles);
}
getEffectivePermissions() {
return this.model
.getEffectivePermissions()
.map((permission) => UIFriendlyPermissions[permission]);
}
removeRole(role: string) {
this.model.removeRole(role);
}

View File

@@ -1,22 +1,31 @@
<h1>Users</h1>
<mat-table [dataSource]="dataSubject" class="mat-elevation-z2">
<!-- <ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef>ID</mat-header-cell>
<mat-cell *matCellDef="let user">{{ user.id }}</mat-cell>
</ng-container> -->
<ng-container matColumnDef="username">
<mat-header-cell *matHeaderCellDef>Username</mat-header-cell>
<mat-cell *matCellDef="let user">{{ user.username }}</mat-cell>
</ng-container>
<ng-container matColumnDef="roles">
<mat-header-cell *matHeaderCellDef>Roles</mat-header-cell>
<mat-cell *matCellDef="let user">
<mat-chip-list aria-label="User Roles">
<mat-chip [disableRipple]="true" *ngFor="let role of user.roles">
{{ role }}
</mat-chip>
</mat-chip-list>
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions">
<mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>
<mat-cell *matCellDef="let user">
<button mat-icon-button (click)="editUser(user)">
<mat-icon aria-label="Edit">edit</mat-icon>
</button>
<button mat-icon-button (click)="deleteUser(user)">
<mat-icon class="icon-red" aria-label="Delete">delete</mat-icon>
</button>
</mat-cell>
</ng-container>

View File

@@ -6,3 +6,6 @@ mat-table {
justify-content: end;
}
.icon-red {
color: #F44336;
}

View File

@@ -1,4 +1,5 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { Router } from '@angular/router';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
@@ -8,13 +9,18 @@ import { BehaviorSubject, Subject, throttleTime } from 'rxjs';
import { SnackBarType } from 'src/app/models/snack-bar-type';
import { UserManageService } from 'src/app/services/api/usermanage.service';
import { UtilService } from 'src/app/util/util.service';
import { DeleteConfirmDialogComponent } from './delete-confirm-dialog/delete-confirm-dialog.component';
@Component({
templateUrl: './settings-users.component.html',
styleUrls: ['./settings-users.component.scss'],
})
export class SettingsUsersComponent implements OnInit {
public readonly displayedColumns: string[] = [/*'id',*/ 'username', 'actions'];
public readonly displayedColumns: string[] = [
'username',
'roles',
'actions',
];
public readonly pageSizeOptions: number[] = [5, 10, 25, 100];
public readonly startingPageSize = this.pageSizeOptions[2];
@@ -26,7 +32,8 @@ export class SettingsUsersComponent implements OnInit {
constructor(
private userManageService: UserManageService,
private utilService: UtilService,
private router: Router
private router: Router,
private dialog: MatDialog
) {}
async ngOnInit() {
@@ -34,12 +41,28 @@ export class SettingsUsersComponent implements OnInit {
this.fetchUsers(this.startingPageSize, 0);
}
public addUser() {
this.router.navigate(['/settings/users/add']);
}
public editUser(user: EUser) {
this.router.navigate(['/settings/users/edit', user.username]);
}
public addUser() {
this.router.navigate(['/settings/users/add']);
public deleteUser(user: EUser) {
const dialogRef = this.dialog.open(DeleteConfirmDialogComponent, {
data: user,
});
dialogRef.afterClosed().subscribe(async () => {
const page = this.paginator.pageIndex;
const pageSize = this.paginator.pageSize;
const success = await this.fetchUsers(pageSize, page);
if (!success) {
this.paginator.firstPage();
}
});
}
@AutoUnsubscribe()

View File

@@ -4,17 +4,23 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatTableModule } from '@angular/material/table';
import { DeleteConfirmDialogComponent } from './delete-confirm-dialog/delete-confirm-dialog.component';
import { SettingsUsersEditComponent } from './settings-users-edit/settings-users-edit.component';
import { SettingsUsersComponent } from './settings-users.component';
import { SettingsUsersRoutingModule } from './settings-users.routing.module';
@NgModule({
declarations: [SettingsUsersComponent, SettingsUsersEditComponent],
declarations: [
SettingsUsersComponent,
SettingsUsersEditComponent,
DeleteConfirmDialogComponent,
],
imports: [
CommonModule,
SettingsUsersRoutingModule,
@@ -26,6 +32,7 @@ import { SettingsUsersRoutingModule } from './settings-users.routing.module';
MatInputModule,
MatChipsModule,
MatAutocompleteModule,
MatDialogModule,
FormsModule,
ReactiveFormsModule,
],

View File

@@ -19,9 +19,9 @@
name="username"
required
/>
<mat-error *ngIf="model.username.errors">{{
model.usernameError
}}</mat-error>
<mat-error *ngIf="model.username.errors">
{{ model.usernameError }}
</mat-error>
</mat-form-field>
</div>
<div class="col-12 py-2">
@@ -34,9 +34,9 @@
name="password"
required
/>
<mat-error *ngIf="model.password.errors">{{
model.passwordError
}}</mat-error>
<mat-error *ngIf="model.password.errors">
{{ model.passwordError }}
</mat-error>
</mat-form-field>
</div>
<div class="col-12 py-2">

View File

@@ -17,9 +17,9 @@
name="username"
required
/>
<mat-error *ngIf="model.username.errors">{{
model.usernameError
}}</mat-error>
<mat-error *ngIf="model.username.errors">
{{ model.usernameError }}
</mat-error>
</mat-form-field>
</div>
<div class="col-12 py-2">
@@ -32,9 +32,9 @@
name="password"
required
/>
<mat-error *ngIf="model.password.errors">{{
model.passwordError
}}</mat-error>
<mat-error *ngIf="model.password.errors">
{{ model.passwordError }}
</mat-error>
</mat-form-field>
</div>
<div class="col-12 py-2">
@@ -47,9 +47,9 @@
name="confirmpassword"
required
/>
<mat-error *ngIf="model.passwordConfirm.errors">{{
model.passwordConfirmError
}}</mat-error>
<mat-error *ngIf="model.passwordConfirm.errors">
{{ model.passwordConfirmError }}
</mat-error>
</mat-form-field>
</div>
<div class="col-12 py-2">

View File

@@ -2,6 +2,8 @@ import { Injectable } from '@angular/core';
import {
UserCreateRequest,
UserCreateResponse,
UserDeleteRequest,
UserDeleteResponse,
UserInfoRequest,
UserInfoResponse,
UserListRequest,
@@ -76,4 +78,19 @@ export class UserManageService {
return result;
}
public async deleteUser(username: string): AsyncFailable<EUser> {
const body = {
username,
};
const result = await this.apiService.post(
UserDeleteRequest,
UserDeleteResponse,
'/api/user/delete',
body
);
return result;
}
}

View File

@@ -27,3 +27,20 @@ export const AdminDashboardPermissions: Permissions = [
Permission.RoleManage,
Permission.SysPrefManage,
];
export const UIFriendlyPermissions: {
[key in Permission]: string;
} = {
[Permission.ImageView]: 'View images',
[Permission.ImageUpload]: 'Upload images',
[Permission.UserLogin]: 'Login',
[Permission.UserMe]: 'View own user details',
[Permission.UserRegister]: 'Register',
[Permission.Settings]: 'View available settings',
[Permission.UserManage]: 'Manage users',
[Permission.RoleManage]: 'Manage roles',
[Permission.SysPrefManage]: 'Manage system preferences',
};

View File

@@ -5499,17 +5499,7 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimist@1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
minimist@^1.2.0, minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
"minimist@npm:minimist-lite":
minimist@1.2.5, minimist@^1.2.0, minimist@^1.2.6, "minimist@npm:minimist-lite":
version "2.2.1"
resolved "https://registry.yarnpkg.com/minimist-lite/-/minimist-lite-2.2.1.tgz#abb71db2c9b454d7cf4496868c03e9802de9934d"
integrity sha512-RSrWIRWGYoM2TDe102s7aIyeSipXMIXKb1fSHYx1tAbxAV0z4g2xR6ra3oPzkTqFb0EIUz1H3A/qvYYeDd+/qQ==