Add role edit view
This commit is contained in:
parent
e4575b7f5b
commit
6417651419
|
@ -117,7 +117,7 @@ export class RolesService {
|
|||
return Fail('Cannot modify immutable role');
|
||||
}
|
||||
|
||||
roleToModify.permissions = permissions;
|
||||
roleToModify.permissions = [...new Set(permissions)];
|
||||
|
||||
try {
|
||||
return await this.rolesRepository.save(roleToModify);
|
||||
|
|
|
@ -54,7 +54,7 @@ export class RolesController {
|
|||
return role;
|
||||
}
|
||||
|
||||
@Post('/permissions')
|
||||
@Post('/update')
|
||||
async updateRole(
|
||||
@Body() body: RoleUpdateRequest,
|
||||
): Promise<RoleUpdateResponse> {
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
CreateUsernameError,
|
||||
PasswordValidators,
|
||||
UsernameValidators
|
||||
} from './default-validators';
|
||||
} from './user-validators';
|
||||
import { UserPassModel } from './userpass.model';
|
||||
|
||||
export class LoginControl {
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
CreateUsernameError,
|
||||
PasswordValidators,
|
||||
UsernameValidators
|
||||
} from './default-validators';
|
||||
} from './user-validators';
|
||||
import { UserPassModel } from './userpass.model';
|
||||
|
||||
export class RegisterControl {
|
||||
|
|
26
frontend/src/app/models/forms/role-validators.ts
Normal file
26
frontend/src/app/models/forms/role-validators.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { ValidationErrors, Validators } from '@angular/forms';
|
||||
import { errorsToError } from './util.validator';
|
||||
|
||||
export const RoleNameValidators = [
|
||||
Validators.minLength(4),
|
||||
Validators.maxLength(32),
|
||||
Validators.pattern('^[a-zA-Z0-9]+$'),
|
||||
];
|
||||
|
||||
export const CreateRoleNameError = (
|
||||
errors: ValidationErrors | null
|
||||
): string => {
|
||||
const error = errorsToError(errors);
|
||||
switch (error) {
|
||||
case 'required':
|
||||
return 'Role name is required';
|
||||
case 'minlength':
|
||||
return 'Role name is too short';
|
||||
case 'maxlength':
|
||||
return 'Role name is too long';
|
||||
case 'pattern':
|
||||
return 'Role name can only contain letters and numbers';
|
||||
default:
|
||||
return 'Invalid role name';
|
||||
}
|
||||
};
|
6
frontend/src/app/models/forms/role.model.ts
Normal file
6
frontend/src/app/models/forms/role.model.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
|
||||
export interface RoleModel {
|
||||
name: string;
|
||||
permissions: Permissions;
|
||||
}
|
112
frontend/src/app/models/forms/updaterole.control.ts
Normal file
112
frontend/src/app/models/forms/updaterole.control.ts
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { FormControl } from '@angular/forms';
|
||||
import Fuse from 'fuse.js';
|
||||
import { Permission, Permissions } from 'picsur-shared/dist/dto/permissions';
|
||||
import { PermanentRolesList } from 'picsur-shared/dist/dto/roles.dto';
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { RoleNameValidators } from './role-validators';
|
||||
import { RoleModel } from './role.model';
|
||||
import { CreateUsernameError } from './user-validators';
|
||||
|
||||
export class UpdateRoleControl {
|
||||
// Set once
|
||||
private permissions: Permissions = [];
|
||||
|
||||
// Variables
|
||||
private selectablePermissionsSubject = new BehaviorSubject<Permissions>([]);
|
||||
private permissionsInputSubscription: null | Subscription;
|
||||
|
||||
public rolename = new FormControl('', RoleNameValidators);
|
||||
|
||||
public permissionControl = new FormControl('', []);
|
||||
public selectablePermissions =
|
||||
this.selectablePermissionsSubject.asObservable();
|
||||
public selectedPermissions: Permissions = [];
|
||||
|
||||
public get rolenameValue() {
|
||||
return this.rolename.value;
|
||||
}
|
||||
|
||||
public get rolenameError() {
|
||||
return CreateUsernameError(this.rolename.errors);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.permissionsInputSubscription =
|
||||
this.permissionControl.valueChanges.subscribe((roles) => {
|
||||
this.updateSelectablePermissions();
|
||||
});
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
if (this.permissionsInputSubscription) {
|
||||
this.permissionsInputSubscription.unsubscribe();
|
||||
this.permissionsInputSubscription = null;
|
||||
}
|
||||
}
|
||||
|
||||
public addPermission(role: Permission) {
|
||||
if (!this.selectablePermissionsSubject.value.includes(role)) return;
|
||||
|
||||
this.selectedPermissions.push(role);
|
||||
this.clearInput();
|
||||
}
|
||||
|
||||
public removePermission(role: Permission) {
|
||||
this.selectedPermissions = this.selectedPermissions.filter(
|
||||
(r) => r !== role
|
||||
);
|
||||
this.updateSelectablePermissions();
|
||||
}
|
||||
|
||||
public isRemovable(role: Permission) {
|
||||
if (PermanentRolesList.includes(role)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Data interaction
|
||||
|
||||
public putAllPermissions(permissions: Permissions) {
|
||||
this.permissions = permissions;
|
||||
this.updateSelectablePermissions();
|
||||
}
|
||||
|
||||
public putRoleName(rolename: string) {
|
||||
this.rolename.setValue(rolename);
|
||||
}
|
||||
|
||||
public putPermissions(permissions: Permissions) {
|
||||
this.selectedPermissions = permissions;
|
||||
this.updateSelectablePermissions();
|
||||
}
|
||||
|
||||
public getData(): RoleModel {
|
||||
return {
|
||||
name: this.rolenameValue,
|
||||
permissions: this.selectedPermissions,
|
||||
};
|
||||
}
|
||||
|
||||
// Logic
|
||||
|
||||
private updateSelectablePermissions() {
|
||||
const availablePermissins = this.permissions.filter(
|
||||
(r) => !this.selectedPermissions.includes(r)
|
||||
);
|
||||
|
||||
const searchValue = this.permissionControl.value;
|
||||
if (searchValue && availablePermissins.length > 0) {
|
||||
const fuse = new Fuse(availablePermissins);
|
||||
const result = fuse
|
||||
.search(this.permissionControl.value ?? '')
|
||||
.map((r) => r.item);
|
||||
|
||||
this.selectablePermissionsSubject.next(result);
|
||||
} else {
|
||||
this.selectablePermissionsSubject.next(availablePermissins);
|
||||
}
|
||||
}
|
||||
|
||||
private clearInput() {
|
||||
this.permissionControl.setValue('');
|
||||
}
|
||||
}
|
|
@ -4,13 +4,13 @@ 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';
|
||||
import { FullUserModel } from './fulluser.model';
|
||||
import {
|
||||
CreatePasswordError,
|
||||
CreateUsernameError,
|
||||
PasswordValidators,
|
||||
UsernameValidators
|
||||
} from './default-validators';
|
||||
import { FullUserModel } from './fulluser.model';
|
||||
} from './user-validators';
|
||||
|
||||
export class UpdateUserControl {
|
||||
// Set once
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
import { ValidationErrors, Validators } from '@angular/forms';
|
||||
import { errorsToError } from './util.validator';
|
||||
|
||||
// Match this with user entity in shared lib
|
||||
// (Security is not handled here, this is only for the user)
|
||||
|
||||
function errorsToError(errors: ValidationErrors | null): string {
|
||||
if (errors) {
|
||||
const error = Object.keys(errors)[0];
|
||||
return error;
|
||||
}
|
||||
return 'unkown';
|
||||
}
|
||||
|
||||
export const UsernameValidators = [
|
||||
Validators.minLength(4),
|
||||
Validators.maxLength(32),
|
||||
|
@ -57,3 +50,4 @@ export const CreatePasswordError = (
|
|||
return 'Invalid password';
|
||||
}
|
||||
};
|
||||
|
9
frontend/src/app/models/forms/util.validator.ts
Normal file
9
frontend/src/app/models/forms/util.validator.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { ValidationErrors } from '@angular/forms';
|
||||
|
||||
export function errorsToError(errors: ValidationErrors | null): string {
|
||||
if (errors) {
|
||||
const error = Object.keys(errors)[0];
|
||||
return error;
|
||||
}
|
||||
return 'unkown';
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<ng-container *ngIf="editing">
|
||||
<h1>Editing {{ model.rolenameValue }}</h1>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="adding">
|
||||
<h1>Add new role</h1>
|
||||
</ng-container>
|
||||
|
||||
<form (ngSubmit)="updateUser()">
|
||||
<div class="row" *ngIf="adding">
|
||||
<div class="col-lg-6 col-12">
|
||||
<mat-form-field appearance="outline" color="accent">
|
||||
<mat-label>Role name</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[formControl]="model.rolename"
|
||||
name="rolename"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
required
|
||||
/>
|
||||
<mat-error *ngIf="model.rolename.errors">
|
||||
{{ model.rolenameError }}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-12">
|
||||
<mat-form-field appearance="outline" color="accent">
|
||||
<mat-label>Permissions</mat-label>
|
||||
<mat-chip-list #chipList aria-label="Permissions Selection">
|
||||
<mat-chip
|
||||
*ngFor="let permission of model.selectedPermissions"
|
||||
[removable]="model.isRemovable(permission)"
|
||||
[disabled]="!model.isRemovable(permission)"
|
||||
(removed)="removePermission(permission)"
|
||||
>
|
||||
{{ uiFriendlyPermission(permission) }}
|
||||
<button *ngIf="model.isRemovable(permission)" matChipRemove>
|
||||
<mat-icon>cancel</mat-icon>
|
||||
</button>
|
||||
</mat-chip>
|
||||
<input
|
||||
placeholder="Add permission..."
|
||||
#fruitInput
|
||||
[formControl]="model.permissionControl"
|
||||
[value]="model.permissionControl.value"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="chipList"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
(matChipInputTokenEnd)="addRole($event)"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
/>
|
||||
</mat-chip-list>
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
(optionSelected)="selectedRole($event)"
|
||||
>
|
||||
<mat-option
|
||||
*ngFor="let permission of model.selectablePermissions | async"
|
||||
[value]="permission"
|
||||
>
|
||||
{{ permission }}
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 py-4">
|
||||
<button mat-raised-button color="accent" type="submit">
|
||||
{{ editing ? "Update" : "Add" }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
class="ms-2"
|
||||
color="primary"
|
||||
type="button"
|
||||
(click)="cancel()"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,3 @@
|
|||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
|
||||
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 {
|
||||
Permission,
|
||||
PermissionsList,
|
||||
UIFriendlyPermissions
|
||||
} from 'picsur-shared/dist/dto/permissions';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { UpdateRoleControl } from 'src/app/models/forms/updaterole.control';
|
||||
import { SnackBarType } from 'src/app/models/snack-bar-type';
|
||||
import { RolesService } from 'src/app/services/api/roles.service';
|
||||
import { UtilService } from 'src/app/util/util.service';
|
||||
|
||||
enum EditMode {
|
||||
edit = 'edit',
|
||||
add = 'add',
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-roles-edit',
|
||||
templateUrl: './settings-roles-edit.component.html',
|
||||
styleUrls: ['./settings-roles-edit.component.scss'],
|
||||
})
|
||||
export class SettingsRolesEditComponent implements OnInit {
|
||||
readonly separatorKeysCodes: number[] = [ENTER, COMMA, SPACE];
|
||||
|
||||
private mode: EditMode = EditMode.edit;
|
||||
|
||||
model = new UpdateRoleControl();
|
||||
|
||||
get adding() {
|
||||
return this.mode === EditMode.add;
|
||||
}
|
||||
get editing() {
|
||||
return this.mode === EditMode.edit;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private utilService: UtilService,
|
||||
private rolesService: RolesService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
Promise.all([this.initRole(), this.initPermissions()]).catch(console.error);
|
||||
}
|
||||
|
||||
private async initRole() {
|
||||
const rolename = this.route.snapshot.paramMap.get('role');
|
||||
if (!rolename) {
|
||||
this.mode = EditMode.add;
|
||||
return;
|
||||
}
|
||||
|
||||
this.mode = EditMode.edit;
|
||||
this.model.putRoleName(rolename);
|
||||
|
||||
const role = await this.rolesService.getRole(rolename);
|
||||
if (HasFailed(role)) {
|
||||
this.utilService.showSnackBar('Failed to get role', SnackBarType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.putRoleName(role.name);
|
||||
this.model.putPermissions(role.permissions);
|
||||
}
|
||||
|
||||
private async initPermissions() {
|
||||
this.model.putAllPermissions(PermissionsList);
|
||||
}
|
||||
|
||||
removePermission(permission: Permission) {
|
||||
this.model.removePermission(permission);
|
||||
}
|
||||
|
||||
addRole(event: MatChipInputEvent) {
|
||||
const value = (event.value ?? '').trim();
|
||||
this.model.addPermission(value as Permission);
|
||||
}
|
||||
|
||||
selectedRole(event: MatAutocompleteSelectedEvent): void {
|
||||
this.model.addPermission(event.option.viewValue as Permission);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.router.navigate(['/settings/roles']);
|
||||
}
|
||||
|
||||
uiFriendlyPermission(permission: Permission) {
|
||||
return UIFriendlyPermissions[permission];
|
||||
}
|
||||
|
||||
async updateUser() {
|
||||
const data = this.model.getData();
|
||||
|
||||
if (this.adding) {
|
||||
const resultRole = await this.rolesService.createRole(data);
|
||||
if (HasFailed(resultRole)) {
|
||||
this.utilService.showSnackBar(
|
||||
'Failed to create role',
|
||||
SnackBarType.Error
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.utilService.showSnackBar('Role created', SnackBarType.Success);
|
||||
} else {
|
||||
const resultRole = await this.rolesService.updateRole(data);
|
||||
if (HasFailed(resultRole)) {
|
||||
this.utilService.showSnackBar(
|
||||
'Failed to update role',
|
||||
SnackBarType.Error
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.utilService.showSnackBar('Role updated', SnackBarType.Success);
|
||||
}
|
||||
|
||||
this.router.navigate(['/settings/roles']);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,9 @@
|
|||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="permissions">
|
||||
<mat-header-cell class="d-none d-md-flex" *matHeaderCellDef>Permissions</mat-header-cell>
|
||||
<mat-header-cell class="d-none d-md-flex" *matHeaderCellDef
|
||||
>Permissions</mat-header-cell
|
||||
>
|
||||
<mat-cell class="d-none d-md-flex" *matCellDef="let role">
|
||||
<mat-chip-list aria-label="Role Permissions">
|
||||
<mat-chip
|
||||
|
@ -38,10 +40,14 @@
|
|||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell *matHeaderCellDef>Actions</mat-header-cell>
|
||||
<mat-cell *matCellDef="let role">
|
||||
<button mat-icon-button (click)="editRole(role)">
|
||||
<mat-icon fontSet="material-icons-outlined" aria-label="Edit"
|
||||
>edit</mat-icon
|
||||
>
|
||||
<button
|
||||
*ngIf="!isImmutable(role)"
|
||||
mat-icon-button
|
||||
(click)="editRole(role)"
|
||||
>
|
||||
<mat-icon fontSet="material-icons-outlined" aria-label="Edit">
|
||||
edit
|
||||
</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!isSystem(role)"
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
Permission,
|
||||
UIFriendlyPermissions
|
||||
} from 'picsur-shared/dist/dto/permissions';
|
||||
import { ImmuteableRolesList, SystemRolesList } from 'picsur-shared/dist/dto/roles.dto';
|
||||
import { ERole } from 'picsur-shared/dist/entities/role.entity';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { SnackBarType } from 'src/app/models/snack-bar-type';
|
||||
|
@ -31,18 +33,72 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit {
|
|||
|
||||
constructor(
|
||||
private rolesService: RolesService,
|
||||
private utilService: UtilService
|
||||
private utilService: UtilService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.initRoles().catch(console.error);
|
||||
this.fetchRoles().catch(console.error);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
}
|
||||
|
||||
async initRoles() {
|
||||
addRole() {
|
||||
this.router.navigate(['/settings/roles/add']);
|
||||
}
|
||||
|
||||
editRole(role: ERole) {
|
||||
this.router.navigate(['/settings/roles/edit', role.name]);
|
||||
}
|
||||
|
||||
async deleteRole(role: ERole) {
|
||||
const pressedButton = await this.utilService.showDialog({
|
||||
title: `Are you sure you want to delete ${role.name}?`,
|
||||
description: 'This action cannot be undone.',
|
||||
buttons: [
|
||||
{
|
||||
color: 'red',
|
||||
name: 'delete',
|
||||
text: 'Delete',
|
||||
},
|
||||
{
|
||||
color: 'primary',
|
||||
name: 'cancel',
|
||||
text: 'Cancel',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (pressedButton === 'delete') {
|
||||
const result = await this.rolesService.deleteRole(role.name);
|
||||
if (HasFailed(result)) {
|
||||
this.utilService.showSnackBar(
|
||||
'Failed to delete user',
|
||||
SnackBarType.Error
|
||||
);
|
||||
} else {
|
||||
this.utilService.showSnackBar('User deleted', SnackBarType.Success);
|
||||
}
|
||||
}
|
||||
|
||||
await this.fetchRoles();
|
||||
}
|
||||
|
||||
uiFriendlyPermission(permission: Permission) {
|
||||
return UIFriendlyPermissions[permission];
|
||||
}
|
||||
|
||||
isSystem(role: ERole) {
|
||||
return SystemRolesList.includes(role.name);
|
||||
}
|
||||
|
||||
isImmutable(role: ERole) {
|
||||
return ImmuteableRolesList.includes(role.name);
|
||||
}
|
||||
|
||||
private async fetchRoles() {
|
||||
const roles = await this.rolesService.getRoles();
|
||||
if (HasFailed(roles)) {
|
||||
this.utilService.showSnackBar('Failed to load roles', SnackBarType.Error);
|
||||
|
@ -51,18 +107,4 @@ export class SettingsRolesComponent implements OnInit, AfterViewInit {
|
|||
|
||||
this.dataSource.data = roles;
|
||||
}
|
||||
|
||||
addRole() {}
|
||||
|
||||
editRole(role: ERole) {}
|
||||
|
||||
deleteRole(role: ERole) {}
|
||||
|
||||
uiFriendlyPermission(permission: Permission) {
|
||||
return UIFriendlyPermissions[permission];
|
||||
}
|
||||
|
||||
isSystem(role: ERole) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
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 { 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 { SettingsRolesEditComponent } from './settings-roles-edit/settings-roles-edit.component';
|
||||
import { SettingsRolesComponent } from './settings-roles.component';
|
||||
import { SettingsRolesRoutingModule } from './settings-roles.routing.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SettingsRolesComponent],
|
||||
declarations: [SettingsRolesComponent, SettingsRolesEditComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SettingsRolesRoutingModule,
|
||||
|
@ -18,6 +23,11 @@ import { SettingsRolesRoutingModule } from './settings-roles.routing.module';
|
|||
MatTableModule,
|
||||
MatChipsModule,
|
||||
MatPaginatorModule,
|
||||
FormsModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatAutocompleteModule
|
||||
],
|
||||
})
|
||||
export class SettingsRolesRouteModule {}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { PRoutes } from 'src/app/models/picsur-routes';
|
||||
import { SettingsRolesEditComponent } from './settings-roles-edit/settings-roles-edit.component';
|
||||
import { SettingsRolesComponent } from './settings-roles.component';
|
||||
|
||||
const routes: PRoutes = [
|
||||
|
@ -8,6 +9,14 @@ const routes: PRoutes = [
|
|||
path: '',
|
||||
component: SettingsRolesComponent,
|
||||
},
|
||||
{
|
||||
path: 'edit/:role',
|
||||
component: SettingsRolesEditComponent,
|
||||
},
|
||||
{
|
||||
path: 'add',
|
||||
component: SettingsRolesEditComponent,
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -6,13 +6,6 @@
|
|||
</ng-container>
|
||||
|
||||
<form (ngSubmit)="updateUser()">
|
||||
<div class="row">
|
||||
<div class="col-12 py-2" *ngIf="updateFail">
|
||||
<mat-error *ngIf="adding"> Failed to add user </mat-error>
|
||||
<mat-error *ngIf="editing"> Failed to update user </mat-error>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" *ngIf="adding">
|
||||
<div class="col-lg-6 col-12">
|
||||
<mat-form-field appearance="outline" color="accent">
|
||||
|
|
|
@ -29,7 +29,6 @@ export class SettingsUsersEditComponent implements OnInit {
|
|||
private mode: EditMode = EditMode.edit;
|
||||
|
||||
model = new UpdateUserControl();
|
||||
updateFail: boolean = false;
|
||||
|
||||
get adding() {
|
||||
return this.mode === EditMode.add;
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { RoleListResponse } from 'picsur-shared/dist/dto/api/roles.dto';
|
||||
import {
|
||||
RoleCreateRequest,
|
||||
RoleCreateResponse,
|
||||
RoleDeleteRequest,
|
||||
RoleDeleteResponse,
|
||||
RoleInfoRequest,
|
||||
RoleInfoResponse,
|
||||
RoleListResponse,
|
||||
RoleUpdateRequest,
|
||||
RoleUpdateResponse
|
||||
} from 'picsur-shared/dist/dto/api/roles.dto';
|
||||
import { ERole } from 'picsur-shared/dist/entities/role.entity';
|
||||
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
|
||||
import { RoleModel } from 'src/app/models/forms/role.model';
|
||||
import { ApiService } from './api.service';
|
||||
|
||||
@Injectable({
|
||||
|
@ -22,4 +33,56 @@ export class RolesService {
|
|||
|
||||
return result.roles;
|
||||
}
|
||||
|
||||
public async getRole(name: string): AsyncFailable<ERole> {
|
||||
const body = {
|
||||
name,
|
||||
};
|
||||
|
||||
const result = await this.apiService.post(
|
||||
RoleInfoRequest,
|
||||
RoleInfoResponse,
|
||||
'/api/roles/info',
|
||||
body
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async createRole(role: RoleModel): AsyncFailable<ERole> {
|
||||
const result = await this.apiService.post(
|
||||
RoleCreateRequest,
|
||||
RoleCreateResponse,
|
||||
'/api/roles/create',
|
||||
role
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async updateRole(role: RoleModel): AsyncFailable<ERole> {
|
||||
const result = await this.apiService.post(
|
||||
RoleUpdateRequest,
|
||||
RoleUpdateResponse,
|
||||
'/api/roles/update',
|
||||
role
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async deleteRole(name: string): AsyncFailable<ERole> {
|
||||
const body = {
|
||||
name,
|
||||
};
|
||||
|
||||
const result = await this.apiService.post(
|
||||
RoleDeleteRequest,
|
||||
RoleDeleteResponse,
|
||||
'/api/roles/delete',
|
||||
body
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
export interface DialogButton {
|
||||
|
@ -19,16 +19,12 @@ export interface DialogData {
|
|||
templateUrl: './confirm-dialog.component.html',
|
||||
styleUrls: ['./confirm-dialog.component.scss'],
|
||||
})
|
||||
export class ConfirmDialogComponent implements OnInit {
|
||||
export class ConfirmDialogComponent {
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<ConfirmDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: DialogData,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
console.log('Dialog', this.data);
|
||||
}
|
||||
|
||||
onButton(name: string) {
|
||||
this.dialogRef.close(name);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsDefined, IsEnum, IsInt, IsNotEmpty, IsPositive, IsString, ValidateNested } from 'class-validator';
|
||||
import { ERole } from '../../entities/role.entity';
|
||||
import { Permissions, PermissionsList } from '../permissions';
|
||||
import {
|
||||
IsArray,
|
||||
IsDefined,
|
||||
IsInt, IsPositive,
|
||||
ValidateNested
|
||||
} from 'class-validator';
|
||||
import {
|
||||
ERole,
|
||||
RoleNameObject,
|
||||
RoleNamePermsObject
|
||||
} from '../../entities/role.entity';
|
||||
|
||||
// RoleInfo
|
||||
export class RoleInfoRequest {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class RoleInfoRequest extends RoleNameObject {}
|
||||
export class RoleInfoResponse extends ERole {}
|
||||
|
||||
// RoleList
|
||||
|
@ -27,15 +30,7 @@ export class RoleListResponse {
|
|||
}
|
||||
|
||||
// RoleUpdate
|
||||
export class RoleUpdateRequest {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsArray()
|
||||
@IsEnum(PermissionsList, { each: true })
|
||||
permissions: Permissions;
|
||||
}
|
||||
export class RoleUpdateRequest extends RoleNamePermsObject {}
|
||||
export class RoleUpdateResponse extends ERole {}
|
||||
|
||||
// RoleCreate
|
||||
|
@ -43,8 +38,5 @@ export class RoleCreateRequest extends ERole {}
|
|||
export class RoleCreateResponse extends ERole {}
|
||||
|
||||
// RoleDelete
|
||||
export class RoleDeleteRequest {
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
}
|
||||
export class RoleDeleteRequest extends RoleNameObject {}
|
||||
export class RoleDeleteResponse extends ERole {}
|
||||
|
|
|
@ -5,10 +5,12 @@ import { Permission, Permissions, PermissionsList } from './permissions';
|
|||
|
||||
// These roles can never be removed or added to a user.
|
||||
const PermanentRolesTuple = tuple('guest', 'user');
|
||||
|
||||
// These roles can never be modified
|
||||
const ImmuteableRolesTuple = tuple('admin');
|
||||
// These roles can never be removed from the server
|
||||
const SystemRolesTuple = tuple(...PermanentRolesTuple, ...ImmuteableRolesTuple);
|
||||
|
||||
// These roles will be applied by default to new users
|
||||
export const DefaultRolesList: string[] = ['user'];
|
||||
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
import { IsArray, IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||
import { IsArray, IsEnum } from 'class-validator';
|
||||
import { Permissions, PermissionsList } from '../dto/permissions';
|
||||
import { EntityID } from '../validators/entity-id.validator';
|
||||
import { IsRoleName } from '../validators/role.validators';
|
||||
|
||||
export class ERole {
|
||||
@EntityID()
|
||||
id?: number;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
export class RoleNameObject {
|
||||
@IsRoleName()
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class RoleNamePermsObject extends RoleNameObject {
|
||||
@IsArray()
|
||||
@IsEnum(PermissionsList, { each: true })
|
||||
permissions: Permissions;
|
||||
}
|
||||
|
||||
export class ERole extends RoleNamePermsObject {
|
||||
@EntityID()
|
||||
id?: number;
|
||||
}
|
||||
|
|
9
shared/src/validators/role.validators.ts
Normal file
9
shared/src/validators/role.validators.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { IsAlphanumeric, IsNotEmpty, IsString, Length } from 'class-validator';
|
||||
import { ComposeValidators } from './compose.validator';
|
||||
|
||||
export const IsRoleName = ComposeValidators(
|
||||
IsNotEmpty(),
|
||||
IsString(),
|
||||
Length(4, 32),
|
||||
IsAlphanumeric(),
|
||||
);
|
Loading…
Reference in a new issue