mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-09 09:58:20 -05:00
skeleton user / group admin dialogs [WIP]
This commit is contained in:
parent
bf28a512c6
commit
c7b46ac861
@ -78,6 +78,9 @@ import { StoragePathEditDialogComponent } from './components/common/edit-dialog/
|
|||||||
import { SettingsService } from './services/settings.service'
|
import { SettingsService } from './services/settings.service'
|
||||||
import { TasksComponent } from './components/manage/tasks/tasks.component'
|
import { TasksComponent } from './components/manage/tasks/tasks.component'
|
||||||
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
|
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 localeBe from '@angular/common/locales/be'
|
||||||
import localeCs from '@angular/common/locales/cs'
|
import localeCs from '@angular/common/locales/cs'
|
||||||
@ -183,6 +186,9 @@ function initializeApp(settings: SettingsService) {
|
|||||||
DocumentAsnComponent,
|
DocumentAsnComponent,
|
||||||
DocumentCommentsComponent,
|
DocumentCommentsComponent,
|
||||||
TasksComponent,
|
TasksComponent,
|
||||||
|
UserEditDialogComponent,
|
||||||
|
GroupEditDialogComponent,
|
||||||
|
PermissionsSelectComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
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 { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
|
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
|
||||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-correspondent-edit-dialog',
|
selector: 'app-correspondent-edit-dialog',
|
||||||
@ -13,12 +12,8 @@ import { ToastService } from 'src/app/services/toast.service'
|
|||||||
styleUrls: ['./correspondent-edit-dialog.component.scss'],
|
styleUrls: ['./correspondent-edit-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> {
|
export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> {
|
||||||
constructor(
|
constructor(service: CorrespondentService, activeModal: NgbActiveModal) {
|
||||||
service: CorrespondentService,
|
super(service, activeModal)
|
||||||
activeModal: NgbActiveModal,
|
|
||||||
toastService: ToastService
|
|
||||||
) {
|
|
||||||
super(service, activeModal, toastService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateTitle() {
|
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 { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
|
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
|
||||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-document-type-edit-dialog',
|
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'],
|
styleUrls: ['./document-type-edit-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> {
|
export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> {
|
||||||
constructor(
|
constructor(service: DocumentTypeService, activeModal: NgbActiveModal) {
|
||||||
service: DocumentTypeService,
|
super(service, activeModal)
|
||||||
activeModal: NgbActiveModal,
|
|
||||||
toastService: ToastService
|
|
||||||
) {
|
|
||||||
super(service, activeModal, toastService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateTitle() {
|
getCreateTitle() {
|
||||||
|
@ -14,8 +14,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId>
|
|||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private service: AbstractPaperlessService<T>,
|
private service: AbstractPaperlessService<T>,
|
||||||
private activeModal: NgbActiveModal,
|
private activeModal: NgbActiveModal
|
||||||
private toastService: ToastService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Input()
|
@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 { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-storage-path-edit-dialog',
|
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'],
|
styleUrls: ['./storage-path-edit-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class StoragePathEditDialogComponent extends EditDialogComponent<PaperlessStoragePath> {
|
export class StoragePathEditDialogComponent extends EditDialogComponent<PaperlessStoragePath> {
|
||||||
constructor(
|
constructor(service: StoragePathService, activeModal: NgbActiveModal) {
|
||||||
service: StoragePathService,
|
super(service, activeModal)
|
||||||
activeModal: NgbActiveModal,
|
|
||||||
toastService: ToastService
|
|
||||||
) {
|
|
||||||
super(service, activeModal, toastService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get pathHint() {
|
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 { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
||||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||||
import { TagService } from 'src/app/services/rest/tag.service'
|
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 { randomColor } from 'src/app/utils/color'
|
||||||
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
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'],
|
styleUrls: ['./tag-edit-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
|
export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
|
||||||
constructor(
|
constructor(service: TagService, activeModal: NgbActiveModal) {
|
||||||
service: TagService,
|
super(service, activeModal)
|
||||||
activeModal: NgbActiveModal,
|
|
||||||
toastService: ToastService
|
|
||||||
) {
|
|
||||||
super(service, activeModal, toastService)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateTitle() {
|
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"
|
addTagText="Add item"
|
||||||
i18n-addTagText="Used for both types, correspondents, storage paths"
|
i18n-addTagText="Used for both types, correspondents, storage paths"
|
||||||
[placeholder]="placeholder"
|
[placeholder]="placeholder"
|
||||||
|
[multiple]="multiple"
|
||||||
bindLabel="name"
|
bindLabel="name"
|
||||||
bindValue="id"
|
bindValue="id"
|
||||||
(change)="onChange(value)"
|
(change)="onChange(value)"
|
||||||
|
@ -44,6 +44,9 @@ export class SelectComponent extends AbstractInputComponent<number> {
|
|||||||
@Input()
|
@Input()
|
||||||
placeholder: string
|
placeholder: string
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
multiple: boolean = false
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
createNew = new EventEmitter<string>()
|
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>
|
</ng-template>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
|
|
||||||
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
|
<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 { ViewportScroller } from '@angular/common'
|
||||||
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
||||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
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 { 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 {
|
enum SettingsNavIDs {
|
||||||
General = 1,
|
General = 1,
|
||||||
@ -54,6 +61,8 @@ export class SettingsComponent
|
|||||||
activeNavID: number
|
activeNavID: number
|
||||||
|
|
||||||
savedViewGroup = new FormGroup({})
|
savedViewGroup = new FormGroup({})
|
||||||
|
usersGroup = new FormGroup({})
|
||||||
|
groupsGroup = new FormGroup({})
|
||||||
|
|
||||||
settingsForm = new FormGroup({
|
settingsForm = new FormGroup({
|
||||||
bulkEditConfirmationDialogs: new FormControl(null),
|
bulkEditConfirmationDialogs: new FormControl(null),
|
||||||
@ -75,6 +84,8 @@ export class SettingsComponent
|
|||||||
notificationsConsumerSuppressOnDashboard: new FormControl(null),
|
notificationsConsumerSuppressOnDashboard: new FormControl(null),
|
||||||
commentsEnabled: new FormControl(null),
|
commentsEnabled: new FormControl(null),
|
||||||
updateCheckingEnabled: new FormControl(null),
|
updateCheckingEnabled: new FormControl(null),
|
||||||
|
usersGroup: this.usersGroup,
|
||||||
|
groupsGroup: this.groupsGroup,
|
||||||
})
|
})
|
||||||
|
|
||||||
savedViews: PaperlessSavedView[]
|
savedViews: PaperlessSavedView[]
|
||||||
@ -86,6 +97,9 @@ export class SettingsComponent
|
|||||||
unsubscribeNotifier: Subject<any> = new Subject()
|
unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
savePending: boolean = false
|
savePending: boolean = false
|
||||||
|
|
||||||
|
users: PaperlessUser[]
|
||||||
|
groups: PaperlessGroup[]
|
||||||
|
|
||||||
get computedDateLocale(): string {
|
get computedDateLocale(): string {
|
||||||
return (
|
return (
|
||||||
this.settingsForm.value.dateLocale ||
|
this.settingsForm.value.dateLocale ||
|
||||||
@ -102,7 +116,10 @@ export class SettingsComponent
|
|||||||
@Inject(LOCALE_ID) public currentLocale: string,
|
@Inject(LOCALE_ID) public currentLocale: string,
|
||||||
private viewportScroller: ViewportScroller,
|
private viewportScroller: ViewportScroller,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
public readonly tourService: TourService
|
public readonly tourService: TourService,
|
||||||
|
private usersService: UserService,
|
||||||
|
private groupsService: GroupService,
|
||||||
|
private modalService: NgbModal
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.settings.settingsSaved.subscribe(() => {
|
this.settings.settingsSaved.subscribe(() => {
|
||||||
@ -159,6 +176,8 @@ export class SettingsComponent
|
|||||||
updateCheckingEnabled: this.settings.get(
|
updateCheckingEnabled: this.settings.get(
|
||||||
SETTINGS_KEYS.UPDATE_CHECKING_ENABLED
|
SETTINGS_KEYS.UPDATE_CHECKING_ENABLED
|
||||||
),
|
),
|
||||||
|
usersGroup: {},
|
||||||
|
groupsGroup: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +195,18 @@ export class SettingsComponent
|
|||||||
this.savedViews = r.results
|
this.savedViews = r.results
|
||||||
this.initialize()
|
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.store = new BehaviorSubject(storeData)
|
||||||
|
|
||||||
this.storeSub = this.store.asObservable().subscribe((state) => {
|
this.storeSub = this.store.asObservable().subscribe((state) => {
|
||||||
@ -400,4 +475,86 @@ export class SettingsComponent
|
|||||||
clearThemeColor() {
|
clearThemeColor() {
|
||||||
this.settingsForm.get('themeColor').patchValue('')
|
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',
|
Log = 'admin.%s_logentry',
|
||||||
MailAccount = 'paperless_mail.%s_mailaccount',
|
MailAccount = 'paperless_mail.%s_mailaccount',
|
||||||
MailRule = 'paperless_mail.%s_mailrule',
|
MailRule = 'paperless_mail.%s_mailrule',
|
||||||
Auth = 'auth.%s_user',
|
User = 'auth.%s_user',
|
||||||
Admin = 'admin.%s_logentry',
|
Admin = 'admin.%s_logentry',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,4 +46,30 @@ export class PermissionsService {
|
|||||||
private getPermissionCode(permission: PaperlessPermission): string {
|
private getPermissionCode(permission: PaperlessPermission): string {
|
||||||
return permission.type.replace('%s', permission.action)
|
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):
|
class UserViewSet(ModelViewSet):
|
||||||
model = User
|
model = User
|
||||||
|
|
||||||
queryset = User.objects.order_by(Lower("username"))
|
queryset = User.objects.exclude(username="consumer").order_by(Lower("username"))
|
||||||
|
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
pagination_class = StandardPagination
|
pagination_class = StandardPagination
|
||||||
|
Loading…
x
Reference in New Issue
Block a user