mirror of
https://github.com/CaramelFur/Picsur.git
synced 2026-05-07 09:26:39 +02:00
add user delete button
This commit is contained in:
@@ -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[]) {
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
.button-red {
|
||||
background-color: #C62828;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -6,3 +6,6 @@ mat-table {
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.icon-red {
|
||||
color: #F44336;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -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==
|
||||
|
||||
Reference in New Issue
Block a user