mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-28 03:46:06 -05:00 
			
		
		
		
	skeleton user / group admin dialogs [WIP]
This commit is contained in:
		| @@ -78,6 +78,9 @@ import { StoragePathEditDialogComponent } from './components/common/edit-dialog/ | ||||
| import { SettingsService } from './services/settings.service' | ||||
| import { TasksComponent } from './components/manage/tasks/tasks.component' | ||||
| import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap' | ||||
| import { UserEditDialogComponent } from './components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component' | ||||
| import { GroupEditDialogComponent } from './components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component' | ||||
| import { PermissionsSelectComponent } from './components/common/permissions-select/permissions-select.component' | ||||
|  | ||||
| import localeBe from '@angular/common/locales/be' | ||||
| import localeCs from '@angular/common/locales/cs' | ||||
| @@ -183,6 +186,9 @@ function initializeApp(settings: SettingsService) { | ||||
|     DocumentAsnComponent, | ||||
|     DocumentCommentsComponent, | ||||
|     TasksComponent, | ||||
|     UserEditDialogComponent, | ||||
|     GroupEditDialogComponent, | ||||
|     PermissionsSelectComponent, | ||||
|   ], | ||||
|   imports: [ | ||||
|     BrowserModule, | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit- | ||||
| import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' | ||||
| import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent' | ||||
| import { CorrespondentService } from 'src/app/services/rest/correspondent.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-correspondent-edit-dialog', | ||||
| @@ -13,12 +12,8 @@ import { ToastService } from 'src/app/services/toast.service' | ||||
|   styleUrls: ['./correspondent-edit-dialog.component.scss'], | ||||
| }) | ||||
| export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> { | ||||
|   constructor( | ||||
|     service: CorrespondentService, | ||||
|     activeModal: NgbActiveModal, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
|     super(service, activeModal, toastService) | ||||
|   constructor(service: CorrespondentService, activeModal: NgbActiveModal) { | ||||
|     super(service, activeModal) | ||||
|   } | ||||
|  | ||||
|   getCreateTitle() { | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit- | ||||
| import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' | ||||
| import { PaperlessDocumentType } from 'src/app/data/paperless-document-type' | ||||
| import { DocumentTypeService } from 'src/app/services/rest/document-type.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-document-type-edit-dialog', | ||||
| @@ -13,12 +12,8 @@ import { ToastService } from 'src/app/services/toast.service' | ||||
|   styleUrls: ['./document-type-edit-dialog.component.scss'], | ||||
| }) | ||||
| export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> { | ||||
|   constructor( | ||||
|     service: DocumentTypeService, | ||||
|     activeModal: NgbActiveModal, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
|     super(service, activeModal, toastService) | ||||
|   constructor(service: DocumentTypeService, activeModal: NgbActiveModal) { | ||||
|     super(service, activeModal) | ||||
|   } | ||||
|  | ||||
|   getCreateTitle() { | ||||
|   | ||||
| @@ -14,8 +14,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId> | ||||
| { | ||||
|   constructor( | ||||
|     private service: AbstractPaperlessService<T>, | ||||
|     private activeModal: NgbActiveModal, | ||||
|     private toastService: ToastService | ||||
|     private activeModal: NgbActiveModal | ||||
|   ) {} | ||||
|  | ||||
|   @Input() | ||||
|   | ||||
| @@ -0,0 +1,19 @@ | ||||
| <form [formGroup]="objectForm" (ngSubmit)="save()"> | ||||
|     <div class="modal-header"> | ||||
|       <h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4> | ||||
|       <button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()"> | ||||
|       </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|       <div class="row"> | ||||
|         <div class="col"> | ||||
|           <app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text> | ||||
|           <app-permissions-select i18n-title title="Permissions" formControlName="permissions"></app-permissions-select> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|       <button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button> | ||||
|       <button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button> | ||||
|     </div> | ||||
|   </form> | ||||
| @@ -0,0 +1,32 @@ | ||||
| import { Component } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { PaperlessGroup } from 'src/app/data/paperless-group' | ||||
| import { GroupService } from 'src/app/services/rest/group.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-group-edit-dialog', | ||||
|   templateUrl: './group-edit-dialog.component.html', | ||||
|   styleUrls: ['./group-edit-dialog.component.scss'], | ||||
| }) | ||||
| export class GroupEditDialogComponent extends EditDialogComponent<PaperlessGroup> { | ||||
|   constructor(service: GroupService, activeModal: NgbActiveModal) { | ||||
|     super(service, activeModal) | ||||
|   } | ||||
|  | ||||
|   getCreateTitle() { | ||||
|     return $localize`Create new user group` | ||||
|   } | ||||
|  | ||||
|   getEditTitle() { | ||||
|     return $localize`Edit user group` | ||||
|   } | ||||
|  | ||||
|   getForm(): FormGroup { | ||||
|     return new FormGroup({ | ||||
|       name: new FormControl(''), | ||||
|       permissions: new FormControl(''), | ||||
|     }) | ||||
|   } | ||||
| } | ||||
| @@ -5,7 +5,6 @@ import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit- | ||||
| import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' | ||||
| import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path' | ||||
| import { StoragePathService } from 'src/app/services/rest/storage-path.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-storage-path-edit-dialog', | ||||
| @@ -13,12 +12,8 @@ import { ToastService } from 'src/app/services/toast.service' | ||||
|   styleUrls: ['./storage-path-edit-dialog.component.scss'], | ||||
| }) | ||||
| export class StoragePathEditDialogComponent extends EditDialogComponent<PaperlessStoragePath> { | ||||
|   constructor( | ||||
|     service: StoragePathService, | ||||
|     activeModal: NgbActiveModal, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
|     super(service, activeModal, toastService) | ||||
|   constructor(service: StoragePathService, activeModal: NgbActiveModal) { | ||||
|     super(service, activeModal) | ||||
|   } | ||||
|  | ||||
|   get pathHint() { | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { PaperlessTag } from 'src/app/data/paperless-tag' | ||||
| import { TagService } from 'src/app/services/rest/tag.service' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { randomColor } from 'src/app/utils/color' | ||||
| import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' | ||||
|  | ||||
| @@ -14,12 +13,8 @@ import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' | ||||
|   styleUrls: ['./tag-edit-dialog.component.scss'], | ||||
| }) | ||||
| export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> { | ||||
|   constructor( | ||||
|     service: TagService, | ||||
|     activeModal: NgbActiveModal, | ||||
|     toastService: ToastService | ||||
|   ) { | ||||
|     super(service, activeModal, toastService) | ||||
|   constructor(service: TagService, activeModal: NgbActiveModal) { | ||||
|     super(service, activeModal) | ||||
|   } | ||||
|  | ||||
|   getCreateTitle() { | ||||
|   | ||||
| @@ -0,0 +1,34 @@ | ||||
| <form [formGroup]="objectForm" (ngSubmit)="save()"> | ||||
|     <div class="modal-header"> | ||||
|       <h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4> | ||||
|       <button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()"> | ||||
|       </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|       <div class="row"> | ||||
|         <div class="col"> | ||||
|           <app-input-text i18n-title title="Username" formControlName="username" [error]="error?.username"></app-input-text> | ||||
|           <app-input-text i18n-title title="First name" formControlName="first_name" [error]="error?.first_name"></app-input-text> | ||||
|           <app-input-text i18n-title title="Last name" formControlName="last_name" [error]="error?.first_name"></app-input-text> | ||||
|  | ||||
|           <div class="form-check form-switch"> | ||||
|             <input type="checkbox" class="form-check-input" id="is_active" formControlName="is_active"> | ||||
|             <label class="form-check-label" for="is_active" i18n>Active</label> | ||||
|           </div> | ||||
|  | ||||
|           <div class="form-check form-switch"> | ||||
|             <input type="checkbox" class="form-check-input" id="is_superuser" formControlName="is_superuser"> | ||||
|             <label class="form-check-label" for="is_superuser" i18n>Superuser</label> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="col"> | ||||
|           <app-input-select i18n-title title="Groups" [items]="groups" multiple="true" formControlName="groups"></app-input-select> | ||||
|           <app-permissions-select i18n-title title="Permissions" formControlName="permissions"></app-permissions-select> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|       <button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button> | ||||
|       <button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button> | ||||
|     </div> | ||||
|   </form> | ||||
| @@ -0,0 +1,51 @@ | ||||
| import { Component } from '@angular/core' | ||||
| import { FormControl, FormGroup } from '@angular/forms' | ||||
| import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { first } from 'rxjs' | ||||
| import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component' | ||||
| import { PaperlessGroup } from 'src/app/data/paperless-group' | ||||
| import { PaperlessUser } from 'src/app/data/paperless-user' | ||||
| import { GroupService } from 'src/app/services/rest/group.service' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-user-edit-dialog', | ||||
|   templateUrl: './user-edit-dialog.component.html', | ||||
|   styleUrls: ['./user-edit-dialog.component.scss'], | ||||
| }) | ||||
| export class UserEditDialogComponent extends EditDialogComponent<PaperlessUser> { | ||||
|   groups: PaperlessGroup[] | ||||
|  | ||||
|   constructor( | ||||
|     service: UserService, | ||||
|     activeModal: NgbActiveModal, | ||||
|     groupsService: GroupService | ||||
|   ) { | ||||
|     super(service, activeModal) | ||||
|  | ||||
|     groupsService | ||||
|       .listAll() | ||||
|       .pipe(first()) | ||||
|       .subscribe((result) => (this.groups = result.results)) | ||||
|   } | ||||
|  | ||||
|   getCreateTitle() { | ||||
|     return $localize`Create new user account` | ||||
|   } | ||||
|  | ||||
|   getEditTitle() { | ||||
|     return $localize`Edit user account` | ||||
|   } | ||||
|  | ||||
|   getForm(): FormGroup { | ||||
|     return new FormGroup({ | ||||
|       username: new FormControl(''), | ||||
|       first_name: new FormControl(''), | ||||
|       last_name: new FormControl(''), | ||||
|       is_active: new FormControl(''), | ||||
|       is_superuser: new FormControl(''), | ||||
|       groups: new FormControl(''), | ||||
|       permissions: new FormControl(''), | ||||
|     }) | ||||
|   } | ||||
| } | ||||
| @@ -11,6 +11,7 @@ | ||||
|         addTagText="Add item" | ||||
|         i18n-addTagText="Used for both types, correspondents, storage paths" | ||||
|         [placeholder]="placeholder" | ||||
|         [multiple]="multiple" | ||||
|         bindLabel="name" | ||||
|         bindValue="id" | ||||
|         (change)="onChange(value)" | ||||
|   | ||||
| @@ -44,6 +44,9 @@ export class SelectComponent extends AbstractInputComponent<number> { | ||||
|   @Input() | ||||
|   placeholder: string | ||||
|  | ||||
|   @Input() | ||||
|   multiple: boolean = false | ||||
|  | ||||
|   @Output() | ||||
|   createNew = new EventEmitter<string>() | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| <form [formGroup]="form"> | ||||
|   <label>{{title}}</label> | ||||
|   <ul class="list-group"> | ||||
|     <li class="list-group-item" *ngFor="let type of PermissionType | keyvalue" [formGroupName]="type.key"> | ||||
|       {{type.key}}: | ||||
|  | ||||
|       <div class="form-check form-check-inline form-switch"> | ||||
|         <input type="checkbox" class="form-check-input" id="{{type.key}}_all" formControlName="all"> | ||||
|         <label class="form-check-label" for="{{type.key}}_all" i18n>All</label> | ||||
|       </div> | ||||
|  | ||||
|       <div *ngFor="let action of PermissionAction | keyvalue" class="form-check form-check-inline" [disabled]="isAll(type.key)"> | ||||
|         <input type="checkbox" class="form-check-input" id="{{type.key}}_{{action.key}}" formControlName="{{action.key}}"> | ||||
|         <label class="form-check-label" for="{{type.key}}_{{action.key}}" i18n>{{action.key}}</label> | ||||
|       </div> | ||||
|     </li> | ||||
|   </ul> | ||||
| </form> | ||||
| @@ -0,0 +1,79 @@ | ||||
| import { Component, forwardRef, Input, OnInit } from '@angular/core' | ||||
| import { | ||||
|   ControlValueAccessor, | ||||
|   FormControl, | ||||
|   FormGroup, | ||||
|   NG_VALUE_ACCESSOR, | ||||
| } from '@angular/forms' | ||||
| import { | ||||
|   PermissionAction, | ||||
|   PermissionsService, | ||||
|   PermissionType, | ||||
| } from 'src/app/services/permissions.service' | ||||
| import { AbstractInputComponent } from '../input/abstract-input' | ||||
|  | ||||
| @Component({ | ||||
|   providers: [ | ||||
|     { | ||||
|       provide: NG_VALUE_ACCESSOR, | ||||
|       useExisting: forwardRef(() => PermissionsSelectComponent), | ||||
|       multi: true, | ||||
|     }, | ||||
|   ], | ||||
|   selector: 'app-permissions-select', | ||||
|   templateUrl: './permissions-select.component.html', | ||||
|   styleUrls: ['./permissions-select.component.scss'], | ||||
| }) | ||||
| export class PermissionsSelectComponent | ||||
|   implements OnInit, ControlValueAccessor | ||||
| { | ||||
|   PermissionType = PermissionType | ||||
|   PermissionAction = PermissionAction | ||||
|  | ||||
|   @Input() | ||||
|   title: string = 'Permissions' | ||||
|  | ||||
|   permissions: string[] | ||||
|  | ||||
|   form = new FormGroup({}) | ||||
|  | ||||
|   constructor(private readonly permissionsService: PermissionsService) { | ||||
|     for (const type in PermissionType) { | ||||
|       const control = new FormGroup({}) | ||||
|       control.addControl('all', new FormControl(null)) | ||||
|       for (const action in PermissionAction) { | ||||
|         control.addControl(action, new FormControl(null)) | ||||
|       } | ||||
|       this.form.addControl(type, control) | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   writeValue(permissions: string[]): void { | ||||
|     this.permissions = permissions | ||||
|     this.permissions.forEach((permissionStr) => { | ||||
|       const { actionKey, typeKey } = | ||||
|         this.permissionsService.getPermissionKeys(permissionStr) | ||||
|  | ||||
|       if (actionKey && typeKey) { | ||||
|         if (this.form.get(typeKey)?.get(actionKey)) { | ||||
|           this.form.get(typeKey).get(actionKey).setValue(true) | ||||
|         } | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
|   registerOnChange(fn: any): void { | ||||
|     throw new Error('Method not implemented.') | ||||
|   } | ||||
|   registerOnTouched(fn: any): void { | ||||
|     throw new Error('Method not implemented.') | ||||
|   } | ||||
|   setDisabledState?(isDisabled: boolean): void { | ||||
|     throw new Error('Method not implemented.') | ||||
|   } | ||||
|  | ||||
|   ngOnInit(): void {} | ||||
|  | ||||
|   isAll(key: string): boolean { | ||||
|     return this.form.get(key).get('all').value == true | ||||
|   } | ||||
| } | ||||
| @@ -221,6 +221,82 @@ | ||||
|  | ||||
|       </ng-template> | ||||
|     </li> | ||||
|  | ||||
|     <li [ngbNavItem]="SettingsNavIDs.UsersGroups" *ifPermissions="{ action: PermissionAction.Add, type: PermissionType.User }" (mouseover)="maybeInitializeTab(SettingsNavIDs.UsersGroups)" (focusin)="maybeInitializeTab(SettingsNavIDs.UsersGroups)"> | ||||
|       <a ngbNavLink i18n>Users & Groups</a> | ||||
|       <ng-template ngbNavContent> | ||||
|  | ||||
|       <ng-container *ngIf="users && groups"> | ||||
|         <h4 class="d-flex"> | ||||
|           <ng-container i18n>Users</ng-container> | ||||
|           <button type="button" class="btn btn-sm btn-primary ms-4" (click)="editUser()" i18n>Add User</button> | ||||
|         </h4> | ||||
|         <ul class="list-group" formGroupName="usersGroup"> | ||||
|  | ||||
|           <li class="list-group-item"> | ||||
|             <div class="row"> | ||||
|               <div class="col" i18n>Username</div> | ||||
|               <div class="col" i18n>Name</div> | ||||
|               <div class="col" i18n>Groups</div> | ||||
|               <div class="col" i18n>Actions</div> | ||||
|             </div> | ||||
|           </li> | ||||
|  | ||||
|           <li *ngFor="let user of users" class="list-group-item" [formGroupName]="user.id"> | ||||
|             <div class="row"> | ||||
|               <div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editUser(user)">{{user.username}}</button></div> | ||||
|               <div class="col d-flex align-items-center">{{user.first_name}} {{user.last_name}}</div> | ||||
|               <div class="col d-flex align-items-center">{{user.groups}}</div> | ||||
|               <div class="col"> | ||||
|                 <div class="btn-group"> | ||||
|                   <button class="btn btn-sm btn-primary" type="button" (click)="editUser(user)" i18n>Edit</button> | ||||
|                   <button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteUser(user)" i18n>Delete</button> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </li> | ||||
|         </ul> | ||||
|  | ||||
|         <h4 class="mt-4 d-flex"> | ||||
|           <ng-container i18n>Groups</ng-container> | ||||
|           <button type="button" class="btn btn-sm btn-primary ms-4" (click)="editGroup()" i18n>Add Group</button> | ||||
|         </h4> | ||||
|         <ul *ngIf="groups.length > 0" class="list-group" formGroupName="groupsGroup"> | ||||
|  | ||||
|           <li class="list-group-item"> | ||||
|             <div class="row"> | ||||
|               <div class="col" i18n>Name</div> | ||||
|               <div class="col"></div> | ||||
|               <div class="col"></div> | ||||
|               <div class="col" i18n>Actions</div> | ||||
|             </div> | ||||
|           </li> | ||||
|  | ||||
|           <li *ngFor="let group of groups" class="list-group-item" [formGroupName]="group.id"> | ||||
|             <div class="row"> | ||||
|               <div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editGroup(group)">{{group.name}}</button></div> | ||||
|               <div class="col"></div> | ||||
|               <div class="col"></div> | ||||
|               <div class="col"> | ||||
|                 <div class="btn-group"> | ||||
|                   <button class="btn btn-sm btn-primary" type="button" (click)="editGroup(group)" i18n>Edit</button> | ||||
|                   <button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteGroup(group)" i18n>Delete</button> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </li> | ||||
|         </ul> | ||||
|  | ||||
|         <div *ngIf="groups.length == 0">No groups defined</div> | ||||
|       </ng-container> | ||||
|  | ||||
|       <div *ngIf="!users || !groups"> | ||||
|         <div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div> | ||||
|         <div class="visually-hidden" i18n>Loading...</div> | ||||
|       </div> | ||||
|  | ||||
|       </ng-template> | ||||
|     </li> | ||||
|   </ul> | ||||
|  | ||||
|   <div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div> | ||||
|   | ||||
| @@ -30,8 +30,15 @@ import { ActivatedRoute } from '@angular/router' | ||||
| import { ViewportScroller } from '@angular/common' | ||||
| import { TourService } from 'ngx-ui-tour-ng-bootstrap' | ||||
| import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' | ||||
| import { NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { NgbModal, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap' | ||||
| import { Results } from 'src/app/data/results' | ||||
| import { UserService } from 'src/app/services/rest/user.service' | ||||
| import { GroupService } from 'src/app/services/rest/group.service' | ||||
| import { PaperlessUser } from 'src/app/data/paperless-user' | ||||
| import { PaperlessGroup } from 'src/app/data/paperless-group' | ||||
| import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component' | ||||
| import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' | ||||
| import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component' | ||||
|  | ||||
| enum SettingsNavIDs { | ||||
|   General = 1, | ||||
| @@ -54,6 +61,8 @@ export class SettingsComponent | ||||
|   activeNavID: number | ||||
|  | ||||
|   savedViewGroup = new FormGroup({}) | ||||
|   usersGroup = new FormGroup({}) | ||||
|   groupsGroup = new FormGroup({}) | ||||
|  | ||||
|   settingsForm = new FormGroup({ | ||||
|     bulkEditConfirmationDialogs: new FormControl(null), | ||||
| @@ -75,6 +84,8 @@ export class SettingsComponent | ||||
|     notificationsConsumerSuppressOnDashboard: new FormControl(null), | ||||
|     commentsEnabled: new FormControl(null), | ||||
|     updateCheckingEnabled: new FormControl(null), | ||||
|     usersGroup: this.usersGroup, | ||||
|     groupsGroup: this.groupsGroup, | ||||
|   }) | ||||
|  | ||||
|   savedViews: PaperlessSavedView[] | ||||
| @@ -86,6 +97,9 @@ export class SettingsComponent | ||||
|   unsubscribeNotifier: Subject<any> = new Subject() | ||||
|   savePending: boolean = false | ||||
|  | ||||
|   users: PaperlessUser[] | ||||
|   groups: PaperlessGroup[] | ||||
|  | ||||
|   get computedDateLocale(): string { | ||||
|     return ( | ||||
|       this.settingsForm.value.dateLocale || | ||||
| @@ -102,7 +116,10 @@ export class SettingsComponent | ||||
|     @Inject(LOCALE_ID) public currentLocale: string, | ||||
|     private viewportScroller: ViewportScroller, | ||||
|     private activatedRoute: ActivatedRoute, | ||||
|     public readonly tourService: TourService | ||||
|     public readonly tourService: TourService, | ||||
|     private usersService: UserService, | ||||
|     private groupsService: GroupService, | ||||
|     private modalService: NgbModal | ||||
|   ) { | ||||
|     super() | ||||
|     this.settings.settingsSaved.subscribe(() => { | ||||
| @@ -159,6 +176,8 @@ export class SettingsComponent | ||||
|       updateCheckingEnabled: this.settings.get( | ||||
|         SETTINGS_KEYS.UPDATE_CHECKING_ENABLED | ||||
|       ), | ||||
|       usersGroup: {}, | ||||
|       groupsGroup: {}, | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -176,6 +195,18 @@ export class SettingsComponent | ||||
|         this.savedViews = r.results | ||||
|         this.initialize() | ||||
|       }) | ||||
|     } else if ( | ||||
|       (navID == SettingsNavIDs.UsersGroups && !this.users) || | ||||
|       !this.groups | ||||
|     ) { | ||||
|       this.usersService.listAll().subscribe((r) => { | ||||
|         this.users = r.results | ||||
|  | ||||
|         this.groupsService.listAll().subscribe((r) => { | ||||
|           this.groups = r.results | ||||
|           this.initialize() | ||||
|         }) | ||||
|       }) | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -204,6 +235,50 @@ export class SettingsComponent | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (this.users && this.groups) { | ||||
|       for (let user of this.users) { | ||||
|         storeData.usersGroup[user.id.toString()] = { | ||||
|           id: user.id, | ||||
|           username: user.username, | ||||
|           first_name: user.first_name, | ||||
|           last_name: user.last_name, | ||||
|           is_active: user.is_active, | ||||
|           is_superuser: user.is_superuser, | ||||
|           groups: user.groups, | ||||
|           permissions: user.permissions, | ||||
|         } | ||||
|         this.usersGroup.addControl( | ||||
|           user.id.toString(), | ||||
|           new FormGroup({ | ||||
|             id: new FormControl(null), | ||||
|             username: new FormControl(null), | ||||
|             first_name: new FormControl(null), | ||||
|             last_name: new FormControl(null), | ||||
|             is_active: new FormControl(null), | ||||
|             is_superuser: new FormControl(null), | ||||
|             groups: new FormControl(null), | ||||
|             permissions: new FormControl(null), | ||||
|           }) | ||||
|         ) | ||||
|       } | ||||
|  | ||||
|       for (let group of this.groups) { | ||||
|         storeData.groupsGroup[group.id.toString()] = { | ||||
|           id: group.id, | ||||
|           name: group.name, | ||||
|           permissions: group.permissions, | ||||
|         } | ||||
|         this.groupsGroup.addControl( | ||||
|           group.id.toString(), | ||||
|           new FormGroup({ | ||||
|             id: new FormControl(null), | ||||
|             name: new FormControl(null), | ||||
|             permissions: new FormControl(null), | ||||
|           }) | ||||
|         ) | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     this.store = new BehaviorSubject(storeData) | ||||
|  | ||||
|     this.storeSub = this.store.asObservable().subscribe((state) => { | ||||
| @@ -400,4 +475,86 @@ export class SettingsComponent | ||||
|   clearThemeColor() { | ||||
|     this.settingsForm.get('themeColor').patchValue('') | ||||
|   } | ||||
|  | ||||
|   editUser(user: PaperlessUser) { | ||||
|     var modal = this.modalService.open(UserEditDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|       size: 'xl', | ||||
|     }) | ||||
|     modal.componentInstance.dialogMode = user ? 'edit' : 'create' | ||||
|     modal.componentInstance.object = user | ||||
|     modal.componentInstance.success | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe({ | ||||
|         next: (newUser) => { | ||||
|           this.toastService.showInfo( | ||||
|             $localize`Saved user "${newUser.username}".` | ||||
|           ) | ||||
|           this.usersService.listAll().subscribe((r) => { | ||||
|             this.users = r.results | ||||
|             this.initialize() | ||||
|           }) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|             $localize`Error saving user: ${e.toString()}.` | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   deleteUser(user: PaperlessUser) { | ||||
|     let modal = this.modalService.open(ConfirmDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     modal.componentInstance.title = $localize`Confirm delete user account` | ||||
|     modal.componentInstance.messageBold = $localize`This operation will permanently this user account.` | ||||
|     modal.componentInstance.message = $localize`This operation cannot be undone.` | ||||
|     modal.componentInstance.btnClass = 'btn-danger' | ||||
|     modal.componentInstance.btnCaption = $localize`Proceed` | ||||
|     modal.componentInstance.confirmClicked.subscribe(() => { | ||||
|       modal.componentInstance.buttonsEnabled = false | ||||
|       this.usersService.delete(user) | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   editGroup(group: PaperlessGroup) { | ||||
|     var modal = this.modalService.open(GroupEditDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|       size: 'lg', | ||||
|     }) | ||||
|     modal.componentInstance.dialogMode = group ? 'edit' : 'create' | ||||
|     modal.componentInstance.object = group | ||||
|     modal.componentInstance.success | ||||
|       .pipe(takeUntil(this.unsubscribeNotifier)) | ||||
|       .subscribe({ | ||||
|         next: (newGroup) => { | ||||
|           this.toastService.showInfo($localize`Saved group "${newGroup.name}".`) | ||||
|           this.groupsService.listAll().subscribe((r) => { | ||||
|             this.groups = r.results | ||||
|             this.initialize() | ||||
|           }) | ||||
|         }, | ||||
|         error: (e) => { | ||||
|           this.toastService.showError( | ||||
|             $localize`Error saving group: ${e.toString()}.` | ||||
|           ) | ||||
|         }, | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   deleteGroup(group: PaperlessGroup) { | ||||
|     let modal = this.modalService.open(ConfirmDialogComponent, { | ||||
|       backdrop: 'static', | ||||
|     }) | ||||
|     modal.componentInstance.title = $localize`Confirm delete user group` | ||||
|     modal.componentInstance.messageBold = $localize`This operation will permanently this user group.` | ||||
|     modal.componentInstance.message = $localize`This operation cannot be undone.` | ||||
|     modal.componentInstance.btnClass = 'btn-danger' | ||||
|     modal.componentInstance.btnCaption = $localize`Proceed` | ||||
|     modal.componentInstance.confirmClicked.subscribe(() => { | ||||
|       modal.componentInstance.buttonsEnabled = false | ||||
|       this.groupsService.delete(group) | ||||
|     }) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -20,7 +20,7 @@ export enum PermissionType { | ||||
|   Log = 'admin.%s_logentry', | ||||
|   MailAccount = 'paperless_mail.%s_mailaccount', | ||||
|   MailRule = 'paperless_mail.%s_mailrule', | ||||
|   Auth = 'auth.%s_user', | ||||
|   User = 'auth.%s_user', | ||||
|   Admin = 'admin.%s_logentry', | ||||
| } | ||||
|  | ||||
| @@ -46,4 +46,30 @@ export class PermissionsService { | ||||
|   private getPermissionCode(permission: PaperlessPermission): string { | ||||
|     return permission.type.replace('%s', permission.action) | ||||
|   } | ||||
|  | ||||
|   public getPermissionKeys(permissionStr: string): { | ||||
|     actionKey: string | ||||
|     typeKey: string | ||||
|   } { | ||||
|     const matches = permissionStr.match(/\.(.+)_/) | ||||
|     let typeKey | ||||
|     let actionKey | ||||
|     if (matches?.length > 0) { | ||||
|       const action = matches[1] | ||||
|       const actionIndex = Object.values(PermissionAction).indexOf( | ||||
|         action as PermissionAction | ||||
|       ) | ||||
|       if (actionIndex > -1) { | ||||
|         actionKey = Object.keys(PermissionAction)[actionIndex] | ||||
|       } | ||||
|       const typeIndex = Object.values(PermissionType).indexOf( | ||||
|         permissionStr.replace(action, '%s') as PermissionType | ||||
|       ) | ||||
|       if (typeIndex > -1) { | ||||
|         typeKey = Object.keys(PermissionType)[typeIndex] | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return { actionKey, typeKey } | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class FaviconView(View): | ||||
| class UserViewSet(ModelViewSet): | ||||
|     model = User | ||||
|  | ||||
|     queryset = User.objects.order_by(Lower("username")) | ||||
|     queryset = User.objects.exclude(username="consumer").order_by(Lower("username")) | ||||
|  | ||||
|     serializer_class = UserSerializer | ||||
|     pagination_class = StandardPagination | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon