Merge branch 'dev' into feature-consumption-templates

This commit is contained in:
shamoon 2023-09-21 19:57:36 -07:00
commit 05b1a4fc01
17 changed files with 475 additions and 162 deletions

View File

@ -247,6 +247,13 @@ do not have an owner set.
Note that superusers have access to all objects.
### Default permissions
Default permissions for documents can be set using consumption templates.
For objects created via the web UI (tags, doc types, etc.) the default is to set the current user
as owner and no extra permissions, but you explicitly set these under Settings > Permissions.
### Users and Groups
Paperless-ngx versions after 1.14.0 allow creating and editing users and groups via the 'frontend' UI.

View File

@ -1,18 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { EditDialogMode } from '../edit-dialog.component'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SelectComponent } from '../../input/select/select.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TextComponent } from '../../input/text/text.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { SettingsService } from 'src/app/services/settings.service'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component'
import { EditDialogMode } from '../edit-dialog.component'
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component'
describe('CorrespondentEditDialogComponent', () => {
let component: CorrespondentEditDialogComponent
let settingsService: SettingsService
let fixture: ComponentFixture<CorrespondentEditDialogComponent>
beforeEach(async () => {
@ -36,6 +38,8 @@ describe('CorrespondentEditDialogComponent', () => {
}).compileComponents()
fixture = TestBed.createComponent(CorrespondentEditDialogComponent)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
component = fixture.componentInstance
fixture.detectChanges()

View File

@ -1,18 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { EditDialogMode } from '../edit-dialog.component'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SelectComponent } from '../../input/select/select.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TextComponent } from '../../input/text/text.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { SettingsService } from 'src/app/services/settings.service'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component'
import { EditDialogMode } from '../edit-dialog.component'
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component'
describe('DocumentTypeEditDialogComponent', () => {
let component: DocumentTypeEditDialogComponent
let settingsService: SettingsService
let fixture: ComponentFixture<DocumentTypeEditDialogComponent>
beforeEach(async () => {
@ -36,6 +38,8 @@ describe('DocumentTypeEditDialogComponent', () => {
}).compileComponents()
fixture = TestBed.createComponent(DocumentTypeEditDialogComponent)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
component = fixture.componentInstance
fixture.detectChanges()

View File

@ -1,6 +1,6 @@
import {
HttpClientTestingModule,
HttpTestingController,
HttpClientTestingModule,
} from '@angular/common/http/testing'
import { Component } from '@angular/core'
import {
@ -16,19 +16,20 @@ import {
ReactiveFormsModule,
} from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { of } from 'rxjs'
import {
DEFAULT_MATCHING_ALGORITHM,
MATCH_AUTO,
MATCH_NONE,
MATCH_ALL,
} from 'src/app/data/matching-model'
import { PaperlessTag } from 'src/app/data/paperless-tag'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { TagService } from 'src/app/services/rest/tag.service'
import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service'
import { EditDialogComponent, EditDialogMode } from './edit-dialog.component'
import {
DEFAULT_MATCHING_ALGORITHM,
MATCH_ALL,
MATCH_AUTO,
MATCH_NONE,
} from 'src/app/data/matching-model'
import { of } from 'rxjs'
import { environment } from 'src/environments/environment'
import { EditDialogComponent, EditDialogMode } from './edit-dialog.component'
@Component({
template: `
@ -88,6 +89,7 @@ describe('EditDialogComponent', () => {
let component: TestComponent
let fixture: ComponentFixture<TestComponent>
let tagService: TagService
let settingsService: SettingsService
let activeModal: NgbActiveModal
let httpTestingController: HttpTestingController
@ -110,18 +112,15 @@ describe('EditDialogComponent', () => {
}),
},
},
{
provide: SettingsService,
useValue: {
currentUser,
},
},
SettingsService,
TagService,
],
imports: [HttpClientTestingModule, FormsModule, ReactiveFormsModule],
}).compileComponents()
tagService = TestBed.inject(TagService)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = currentUser
activeModal = TestBed.inject(NgbActiveModal)
httpTestingController = TestBed.inject(HttpTestingController)
@ -149,7 +148,7 @@ describe('EditDialogComponent', () => {
expect(component.closeEnabled).toBeTruthy()
}))
it('should set default owner when in create mode', () => {
it('should set default owner when in create mode if unset', () => {
component.dialogMode = EditDialogMode.CREATE
component.ngOnInit()
expect(component.objectForm.get('permissions_form').value.owner).toEqual(
@ -160,6 +159,32 @@ describe('EditDialogComponent', () => {
component.ngOnInit()
})
it('should set default perms when in create mode if set', () => {
component.dialogMode = EditDialogMode.CREATE
settingsService.set(SETTINGS_KEYS.DEFAULT_PERMS_OWNER, 11)
settingsService.set(SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS, [1, 2])
settingsService.set(SETTINGS_KEYS.DEFAULT_PERMS_VIEW_GROUPS, [3])
settingsService.set(SETTINGS_KEYS.DEFAULT_PERMS_EDIT_USERS, [4])
settingsService.set(SETTINGS_KEYS.DEFAULT_PERMS_EDIT_GROUPS, [5])
component.ngOnInit()
expect(component.objectForm.get('permissions_form').value.owner).toEqual(11)
expect(
component.objectForm.get('permissions_form').value.set_permissions
).toEqual({
view: {
users: [1, 2],
groups: [3],
},
change: {
users: [4],
groups: [5],
},
})
// cover optional chaining
component.objectForm.removeControl('permissions_form')
component.ngOnInit()
})
it('should detect if pattern required', () => {
expect(component.patternRequired).toBeFalsy()
component.objectForm.get('matching_algorithm').setValue(MATCH_AUTO)

View File

@ -14,6 +14,7 @@ import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperle
import { UserService } from 'src/app/services/rest/user.service'
import { PermissionsFormObject } from '../input/permissions/permissions-form/permissions-form.component'
import { SettingsService } from 'src/app/services/settings.service'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
export enum EditDialogMode {
CREATE = 0,
@ -67,6 +68,31 @@ export abstract class EditDialogComponent<
set_permissions: (this.object as ObjectWithPermissions).permissions,
}
this.objectForm.patchValue(this.object)
} else {
// defaults from settings
this.objectForm.patchValue({
permissions_form: {
owner: this.settingsService.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER),
set_permissions: {
view: {
users: this.settingsService.get(
SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS
),
groups: this.settingsService.get(
SETTINGS_KEYS.DEFAULT_PERMS_VIEW_GROUPS
),
},
change: {
users: this.settingsService.get(
SETTINGS_KEYS.DEFAULT_PERMS_EDIT_USERS
),
groups: this.settingsService.get(
SETTINGS_KEYS.DEFAULT_PERMS_EDIT_GROUPS
),
},
},
},
})
}
// wait to enable close button so it doesnt steal focus from input since its the first clickable element in the DOM
@ -76,11 +102,6 @@ export abstract class EditDialogComponent<
this.userService.listAll().subscribe((r) => {
this.users = r.results
if (this.dialogMode === EditDialogMode.CREATE) {
this.objectForm.get('permissions_form')?.setValue({
owner: this.settingsService.currentUser.id,
})
}
})
}

View File

@ -1,19 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { EditDialogMode } from '../edit-dialog.component'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SelectComponent } from '../../input/select/select.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TextComponent } from '../../input/text/text.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { SettingsService } from 'src/app/services/settings.service'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { GroupEditDialogComponent } from './group-edit-dialog.component'
import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component'
import { PermissionsSelectComponent } from '../../permissions-select/permissions-select.component'
import { EditDialogMode } from '../edit-dialog.component'
import { GroupEditDialogComponent } from './group-edit-dialog.component'
describe('GroupEditDialogComponent', () => {
let component: GroupEditDialogComponent
let settingsService: SettingsService
let fixture: ComponentFixture<GroupEditDialogComponent>
beforeEach(async () => {
@ -38,6 +40,8 @@ describe('GroupEditDialogComponent', () => {
}).compileComponents()
fixture = TestBed.createComponent(GroupEditDialogComponent)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
component = fixture.componentInstance
fixture.detectChanges()

View File

@ -1,30 +1,32 @@
import {
HttpTestingController,
HttpClientTestingModule,
} from '@angular/common/http/testing'
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing'
import { EditDialogMode } from '../edit-dialog.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { IMAPSecurity } from 'src/app/data/paperless-mail-account'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SelectComponent } from '../../input/select/select.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TextComponent } from '../../input/text/text.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { MailAccountEditDialogComponent } from './mail-account-edit-dialog.component'
import { PasswordComponent } from '../../input/password/password.component'
import { CheckComponent } from '../../input/check/check.component'
import { IMAPSecurity } from 'src/app/data/paperless-mail-account'
import { SettingsService } from 'src/app/services/settings.service'
import { environment } from 'src/environments/environment'
import { CheckComponent } from '../../input/check/check.component'
import { PasswordComponent } from '../../input/password/password.component'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component'
import { EditDialogMode } from '../edit-dialog.component'
import { MailAccountEditDialogComponent } from './mail-account-edit-dialog.component'
describe('MailAccountEditDialogComponent', () => {
let component: MailAccountEditDialogComponent
let settingsService: SettingsService
let fixture: ComponentFixture<MailAccountEditDialogComponent>
let httpController: HttpTestingController
@ -53,6 +55,8 @@ describe('MailAccountEditDialogComponent', () => {
httpController = TestBed.inject(HttpTestingController)
fixture = TestBed.createComponent(MailAccountEditDialogComponent)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
component = fixture.componentInstance
fixture.detectChanges()

View File

@ -1,30 +1,32 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { EditDialogMode } from '../edit-dialog.component'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SelectComponent } from '../../input/select/select.component'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TextComponent } from '../../input/text/text.component'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { MailRuleEditDialogComponent } from './mail-rule-edit-dialog.component'
import { NumberComponent } from '../../input/number/number.component'
import { TagsComponent } from '../../input/tags/tags.component'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { of } from 'rxjs'
import {
MailAction,
MailMetadataCorrespondentOption,
MailAction,
} from 'src/app/data/paperless-mail-rule'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
import { SettingsService } from 'src/app/services/settings.service'
import { CheckComponent } from '../../input/check/check.component'
import { NumberComponent } from '../../input/number/number.component'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component'
import { TagsComponent } from '../../input/tags/tags.component'
import { TextComponent } from '../../input/text/text.component'
import { EditDialogMode } from '../edit-dialog.component'
import { MailRuleEditDialogComponent } from './mail-rule-edit-dialog.component'
describe('MailRuleEditDialogComponent', () => {
let component: MailRuleEditDialogComponent
let settingsService: SettingsService
let fixture: ComponentFixture<MailRuleEditDialogComponent>
beforeEach(async () => {
@ -72,6 +74,8 @@ describe('MailRuleEditDialogComponent', () => {
}).compileComponents()
fixture = TestBed.createComponent(MailRuleEditDialogComponent)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
component = fixture.componentInstance
fixture.detectChanges()

View File

@ -1,19 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { EditDialogMode } from '../edit-dialog.component'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SelectComponent } from '../../input/select/select.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TextComponent } from '../../input/text/text.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { StoragePathEditDialogComponent } from './storage-path-edit-dialog.component'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { SettingsService } from 'src/app/services/settings.service'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component'
import { EditDialogMode } from '../edit-dialog.component'
import { StoragePathEditDialogComponent } from './storage-path-edit-dialog.component'
describe('StoragePathEditDialogComponent', () => {
let component: StoragePathEditDialogComponent
let settingsService: SettingsService
let fixture: ComponentFixture<StoragePathEditDialogComponent>
beforeEach(async () => {
@ -38,6 +40,8 @@ describe('StoragePathEditDialogComponent', () => {
}).compileComponents()
fixture = TestBed.createComponent(StoragePathEditDialogComponent)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
component = fixture.componentInstance
fixture.detectChanges()

View File

@ -1,20 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { EditDialogMode } from '../edit-dialog.component'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SelectComponent } from '../../input/select/select.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TextComponent } from '../../input/text/text.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { TagEditDialogComponent } from './tag-edit-dialog.component'
import { ColorComponent } from '../../input/color/color.component'
import { SettingsService } from 'src/app/services/settings.service'
import { CheckComponent } from '../../input/check/check.component'
import { ColorComponent } from '../../input/color/color.component'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component'
import { EditDialogMode } from '../edit-dialog.component'
import { TagEditDialogComponent } from './tag-edit-dialog.component'
describe('TagEditDialogComponent', () => {
let component: TagEditDialogComponent
let settingsService: SettingsService
let fixture: ComponentFixture<TagEditDialogComponent>
beforeEach(async () => {
@ -29,7 +31,7 @@ describe('TagEditDialogComponent', () => {
ColorComponent,
CheckComponent,
],
providers: [NgbActiveModal],
providers: [NgbActiveModal, SettingsService],
imports: [
HttpClientTestingModule,
FormsModule,
@ -40,6 +42,8 @@ describe('TagEditDialogComponent', () => {
}).compileComponents()
fixture = TestBed.createComponent(TagEditDialogComponent)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
component = fixture.componentInstance
fixture.detectChanges()

View File

@ -1,26 +1,28 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { EditDialogMode } from '../edit-dialog.component'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { SelectComponent } from '../../input/select/select.component'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import {
AbstractControl,
FormsModule,
ReactiveFormsModule,
AbstractControl,
} from '@angular/forms'
import { TextComponent } from '../../input/text/text.component'
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { UserEditDialogComponent } from './user-edit-dialog.component'
import { PasswordComponent } from '../../input/password/password.component'
import { PermissionsSelectComponent } from '../../permissions-select/permissions-select.component'
import { GroupService } from 'src/app/services/rest/group.service'
import { of } from 'rxjs'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { GroupService } from 'src/app/services/rest/group.service'
import { SettingsService } from 'src/app/services/settings.service'
import { PasswordComponent } from '../../input/password/password.component'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component'
import { PermissionsSelectComponent } from '../../permissions-select/permissions-select.component'
import { EditDialogMode } from '../edit-dialog.component'
import { UserEditDialogComponent } from './user-edit-dialog.component'
describe('UserEditDialogComponent', () => {
let component: UserEditDialogComponent
let settingsService: SettingsService
let fixture: ComponentFixture<UserEditDialogComponent>
beforeEach(async () => {
@ -51,6 +53,7 @@ describe('UserEditDialogComponent', () => {
}),
},
},
SettingsService,
],
imports: [
HttpClientTestingModule,
@ -62,6 +65,8 @@ describe('UserEditDialogComponent', () => {
}).compileComponents()
fixture = TestBed.createComponent(UserEditDialogComponent)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
component = fixture.componentInstance
fixture.detectChanges()

View File

@ -11,14 +11,14 @@
<form [formGroup]="settingsForm" (ngSubmit)="saveSettings()">
<ul ngbNav #nav="ngbNav" (navChange)="onNavChange($event)" [(activeId)]="activeNavID" class="nav-tabs">
<li [ngbNavItem]="SettingsNavIDs.General">
<li [ngbNavItem]="SettingsNavIDs.General" (mouseover)="maybeInitializeTab(SettingsNavIDs.General)">
<a ngbNavLink i18n>General</a>
<ng-template ngbNavContent>
<h4 i18n>Appearance</h4>
<div class="row mb-3">
<div class="col-md-3 col-form-label">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Display language</span>
</div>
<div class="col">
@ -33,7 +33,7 @@
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Date display</span>
</div>
<div class="col">
@ -46,7 +46,7 @@
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Date format</span>
</div>
<div class="col">
@ -68,7 +68,7 @@
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Items per page</span>
</div>
<div class="col">
@ -84,7 +84,7 @@
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Document editor</span>
</div>
<div class="col">
@ -95,7 +95,7 @@
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Sidebar</span>
</div>
<div class="col">
@ -106,7 +106,7 @@
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Dark mode</span>
</div>
<div class="col">
@ -117,7 +117,7 @@
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Theme Color</span>
</div>
<div class="col col-md-3">
@ -132,6 +132,82 @@
</div>
</div>
<h4 i18n>Permissions</h4>
<div class="row mb-3">
<div class="offset-md-3 col">
<p i18n>
Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI
</p>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Default Owner</span>
</div>
<div class="col-md-4">
<pngx-input-select [items]="users" bindLabel="username" formControlName="defaultPermsOwner" [allowNull]="true"></pngx-input-select>
<small class="form-text text-muted text-end d-block mt-n2" i18n>Objects without an owner can be viewed and edited by all users</small>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Default View Permissions</span>
</div>
<div class="col-md-4">
<div class="row">
<div class="col-2">
<span class="d-block pt-1" i18n>Users:</span>
</div>
<div class="col">
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
<pngx-permissions-user type="view" formControlName="defaultPermsViewUsers"></pngx-permissions-user>
</ng-container>
</div>
</div>
<div class="row">
<div class="col-2">
<span class="d-block pt-1" i18n>Groups:</span>
</div>
<div class="col">
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Group }">
<pngx-permissions-group type="view" formControlName="defaultPermsViewGroups"></pngx-permissions-group>
</ng-container>
</div>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Default Edit Permissions</span>
</div>
<div class="col-md-4">
<div class="row">
<div class="col-2">
<span class="d-block pt-1" i18n>Users:</span>
</div>
<div class="col">
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
<pngx-permissions-user type="view" formControlName="defaultPermsEditUsers"></pngx-permissions-user>
</ng-container>
</div>
</div>
<div class="row">
<div class="col-2">
<span class="d-block pt-1" i18n>Groups:</span>
</div>
<div class="col">
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Group }">
<pngx-permissions-group type="view" formControlName="defaultPermsEditGroups"></pngx-permissions-group>
</ng-container>
</div>
</div>
<div class="row">
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
</div>
</div>
</div>
<h4 class="mt-4" id="update-checking" i18n>Update checking</h4>
<div class="row mb-3">

View File

@ -13,10 +13,11 @@ import { RouterTestingModule } from '@angular/router/testing'
import {
NgbModal,
NgbModule,
NgbAlertModule,
NgbNavLink,
NgbModalRef,
NgbAlertModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { of, throwError } from 'rxjs'
import { routes } from 'src/app/app-routing.module'
import { PaperlessMailAccount } from 'src/app/data/paperless-mail-account'
@ -26,6 +27,7 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { PermissionsService } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service'
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
@ -41,15 +43,15 @@ import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
import { CheckComponent } from '../../common/input/check/check.component'
import { ColorComponent } from '../../common/input/color/color.component'
import { NumberComponent } from '../../common/input/number/number.component'
import { PasswordComponent } from '../../common/input/password/password.component'
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component'
import { SelectComponent } from '../../common/input/select/select.component'
import { TagsComponent } from '../../common/input/tags/tags.component'
import { TextComponent } from '../../common/input/text/text.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { SettingsComponent } from './settings.component'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { SelectComponent } from '../../common/input/select/select.component'
import { TextComponent } from '../../common/input/text/text.component'
import { PasswordComponent } from '../../common/input/password/password.component'
import { NumberComponent } from '../../common/input/number/number.component'
import { TagsComponent } from '../../common/input/tags/tags.component'
import { NgSelectModule } from '@ng-select/ng-select'
const savedViews = [
{ id: 1, name: 'view1' },
@ -106,6 +108,8 @@ describe('SettingsComponent', () => {
TagsComponent,
MailAccountEditDialogComponent,
MailRuleEditDialogComponent,
PermissionsUserComponent,
PermissionsGroupComponent,
],
providers: [CustomDatePipe, DatePipe, PermissionsGuard],
imports: [
@ -125,6 +129,7 @@ describe('SettingsComponent', () => {
viewportScroller = TestBed.inject(ViewportScroller)
toastService = TestBed.inject(ToastService)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 99, username: 'user99' }
userService = TestBed.inject(UserService)
permissionsService = TestBed.inject(PermissionsService)
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
@ -247,11 +252,11 @@ describe('SettingsComponent', () => {
)
expect(component.mailAccounts).not.toBeUndefined()
expect(component.users).toBeUndefined()
expect(component.groups).toBeUndefined()
tabButtons[4].nativeElement.dispatchEvent(
new MouseEvent('mouseover', { bubbles: true })
)
expect(component.users).not.toBeUndefined()
expect(component.groups).not.toBeUndefined()
})
it('should support save saved views, show error', () => {
@ -301,7 +306,7 @@ describe('SettingsComponent', () => {
expect(toastErrorSpy).toHaveBeenCalled()
expect(storeSpy).toHaveBeenCalled()
expect(appearanceSettingsSpy).not.toHaveBeenCalled()
expect(setSpy).toHaveBeenCalledTimes(19)
expect(setSpy).toHaveBeenCalledTimes(24)
// succeed
storeSpy.mockReturnValueOnce(of(true))

View File

@ -48,6 +48,7 @@ import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
import {
PermissionAction,
PermissionType,
PermissionsService,
} from 'src/app/services/permissions.service'
@ -93,6 +94,11 @@ export class SettingsComponent
dateFormat: new FormControl(null),
notesEnabled: new FormControl(null),
updateCheckingEnabled: new FormControl(null),
defaultPermsOwner: new FormControl(null),
defaultPermsViewUsers: new FormControl(null),
defaultPermsViewGroups: new FormControl(null),
defaultPermsEditUsers: new FormControl(null),
defaultPermsEditGroups: new FormControl(null),
notificationsConsumerNewDocument: new FormControl(null),
notificationsConsumerSuccess: new FormControl(null),
@ -159,6 +165,16 @@ export class SettingsComponent
this.activatedRoute.paramMap.subscribe((paramMap) => {
const section = paramMap.get('section')
if (section === null) {
if (
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.User
)
) {
this.getUsers()
}
}
if (section) {
const navIDKey: string = Object.keys(SettingsNavIDs).find(
(navID) => navID.toLowerCase() == section
@ -222,6 +238,19 @@ export class SettingsComponent
savedViewsWarnOnUnsavedChange: this.settings.get(
SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE
),
defaultPermsOwner: this.settings.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER),
defaultPermsViewUsers: this.settings.get(
SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS
),
defaultPermsViewGroups: this.settings.get(
SETTINGS_KEYS.DEFAULT_PERMS_VIEW_GROUPS
),
defaultPermsEditUsers: this.settings.get(
SETTINGS_KEYS.DEFAULT_PERMS_EDIT_USERS
),
defaultPermsEditGroups: this.settings.get(
SETTINGS_KEYS.DEFAULT_PERMS_EDIT_GROUPS
),
usersGroup: {},
groupsGroup: {},
savedViews: {},
@ -256,33 +285,21 @@ export class SettingsComponent
this.initialize(false)
})
} else if (
navID == SettingsNavIDs.UsersGroups &&
(navID == SettingsNavIDs.UsersGroups ||
navID == SettingsNavIDs.General) &&
(!this.users || !this.groups)
) {
this.usersService
if (!this.users) this.getUsers()
this.groupsService
.listAll()
.pipe(first())
.subscribe({
next: (r) => {
this.users = r.results
this.groupsService
.listAll()
.pipe(first())
.subscribe({
next: (r) => {
this.groups = r.results
this.initialize(false)
},
error: (e) => {
this.toastService.showError(
$localize`Error retrieving groups`,
e
)
},
})
this.groups = r.results
this.initialize(false)
},
error: (e) => {
this.toastService.showError($localize`Error retrieving users`, e)
this.toastService.showError($localize`Error retrieving groups`, e)
},
})
} else if (
@ -322,6 +339,20 @@ export class SettingsComponent
}
}
private getUsers() {
this.usersService
.listAll()
.pipe(first())
.subscribe({
next: (r) => {
this.users = r.results
},
error: (e) => {
this.toastService.showError($localize`Error retrieving users`, e)
},
})
}
initialize(resetSettings: boolean = true) {
this.unsubscribeNotifier.next(true)
@ -611,6 +642,26 @@ export class SettingsComponent
SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE,
this.settingsForm.value.savedViewsWarnOnUnsavedChange
)
this.settings.set(
SETTINGS_KEYS.DEFAULT_PERMS_OWNER,
this.settingsForm.value.defaultPermsOwner
)
this.settings.set(
SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS,
this.settingsForm.value.defaultPermsViewUsers
)
this.settings.set(
SETTINGS_KEYS.DEFAULT_PERMS_VIEW_GROUPS,
this.settingsForm.value.defaultPermsViewGroups
)
this.settings.set(
SETTINGS_KEYS.DEFAULT_PERMS_EDIT_USERS,
this.settingsForm.value.defaultPermsEditUsers
)
this.settings.set(
SETTINGS_KEYS.DEFAULT_PERMS_EDIT_GROUPS,
this.settingsForm.value.defaultPermsEditGroups
)
this.settings.setLanguage(this.settingsForm.value.displayLanguage)
this.settings
.storeSettings()

View File

@ -42,6 +42,11 @@ export const SETTINGS_KEYS = {
SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE:
'general-settings:saved-views:warn-on-unsaved-change',
TOUR_COMPLETE: 'general-settings:tour-complete',
DEFAULT_PERMS_OWNER: 'general-settings:permissions:default-owner',
DEFAULT_PERMS_VIEW_USERS: 'general-settings:permissions:default-view-users',
DEFAULT_PERMS_VIEW_GROUPS: 'general-settings:permissions:default-view-groups',
DEFAULT_PERMS_EDIT_USERS: 'general-settings:permissions:default-edit-users',
DEFAULT_PERMS_EDIT_GROUPS: 'general-settings:permissions:default-edit-groups',
}
export const SETTINGS: PaperlessUiSetting[] = [
@ -150,4 +155,29 @@ export const SETTINGS: PaperlessUiSetting[] = [
type: 'boolean',
default: false,
},
{
key: SETTINGS_KEYS.DEFAULT_PERMS_OWNER,
type: 'number',
default: undefined,
},
{
key: SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS,
type: 'array',
default: [],
},
{
key: SETTINGS_KEYS.DEFAULT_PERMS_VIEW_GROUPS,
type: 'array',
default: [],
},
{
key: SETTINGS_KEYS.DEFAULT_PERMS_EDIT_USERS,
type: 'array',
default: [],
},
{
key: SETTINGS_KEYS.DEFAULT_PERMS_EDIT_GROUPS,
type: 'array',
default: [],
},
]

View File

@ -1,21 +1,25 @@
import { TestBed } from '@angular/core/testing'
import { SettingsService } from './settings.service'
import {
HttpClientTestingModule,
HttpTestingController,
HttpClientTestingModule,
} from '@angular/common/http/testing'
import { RouterTestingModule } from '@angular/router/testing'
import { environment } from 'src/environments/environment'
import { Subscription } from 'rxjs'
import { PaperlessUiSettings } from '../data/paperless-uisettings'
import { SETTINGS_KEYS } from '../data/paperless-uisettings'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { RouterTestingModule } from '@angular/router/testing'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { CookieService } from 'ngx-cookie-service'
import { Subscription } from 'rxjs'
import { environment } from 'src/environments/environment'
import { AppModule } from '../app.module'
import {
PaperlessUiSettings,
SETTINGS_KEYS,
} from '../data/paperless-uisettings'
import { SettingsService } from './settings.service'
describe('SettingsService', () => {
let httpTestingController: HttpTestingController
let settingsService: SettingsService
let cookieService: CookieService
let subscription: Subscription
const ui_settings: PaperlessUiSettings = {
@ -46,6 +50,13 @@ describe('SettingsService', () => {
saved_views: { warn_on_unsaved_change: true },
notes_enabled: true,
tour_complete: false,
permissions: {
default_owner: null,
default_view_users: [1],
default_view_groups: [2],
default_edit_users: [3],
default_edit_groups: [4],
},
},
permissions: [],
}
@ -53,7 +64,7 @@ describe('SettingsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [],
providers: [SettingsService],
providers: [SettingsService, CookieService],
imports: [
HttpClientTestingModule,
RouterTestingModule,
@ -65,6 +76,7 @@ describe('SettingsService', () => {
})
httpTestingController = TestBed.inject(HttpTestingController)
cookieService = TestBed.inject(CookieService)
settingsService = TestBed.inject(SettingsService)
})
@ -136,7 +148,52 @@ describe('SettingsService', () => {
expect(settingsService.get(SETTINGS_KEYS.THEME_COLOR)).toEqual('#000000')
})
it('updates appearnce settings', () => {
it('sets django cookie for languages', () => {
httpTestingController
.expectOne(`${environment.apiBaseUrl}ui_settings/`)
.flush(ui_settings)
const cookieSetSpy = jest.spyOn(cookieService, 'set')
settingsService.initializeSettings().subscribe(() => {})
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}ui_settings/`
)
ui_settings.settings['language'] = 'foobar'
req.flush(ui_settings)
expect(cookieSetSpy).toHaveBeenCalledWith('django_language', 'foobar')
const cookieDeleteSpy = jest.spyOn(cookieService, 'delete')
settingsService.setLanguage('')
expect(cookieDeleteSpy).toHaveBeenCalled()
})
it('should support null values for settings if set, undefined if not', () => {
httpTestingController
.expectOne(`${environment.apiBaseUrl}ui_settings/`)
.flush(ui_settings)
expect(settingsService.get('foo')).toEqual(undefined)
expect(settingsService.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER)).toEqual(null)
})
it('should support array values', () => {
httpTestingController
.expectOne(`${environment.apiBaseUrl}ui_settings/`)
.flush(ui_settings)
expect(settingsService.get(SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS)).toEqual(
[1]
)
})
it('should support default permissions values', () => {
delete ui_settings.settings['permissions']
httpTestingController
.expectOne(`${environment.apiBaseUrl}ui_settings/`)
.flush(ui_settings)
expect(settingsService.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER)).toEqual(1)
expect(settingsService.get(SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS)).toEqual(
[]
)
})
it('updates appearance settings', () => {
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}ui_settings/`
)

View File

@ -372,7 +372,7 @@ export class SettingsService {
}
private getSettingRawValue(key: string): any {
let value = null
let value = undefined
// parse key:key:key into nested object
const keys = key.replace('general-settings:', '').split(':')
let settingObj = this.settings
@ -389,12 +389,20 @@ export class SettingsService {
let setting = SETTINGS.find((s) => s.key == key)
if (!setting) {
return null
return undefined
}
let value = this.getSettingRawValue(key)
if (value != null) {
// special case to fallback
if (key === SETTINGS_KEYS.DEFAULT_PERMS_OWNER && value === undefined) {
return this.currentUser.id
}
if (value !== undefined) {
if (value === null) {
return null
}
switch (setting.type) {
case 'boolean':
return JSON.parse(value)
@ -424,7 +432,7 @@ export class SettingsService {
private settingIsSet(key: string): boolean {
let value = this.getSettingRawValue(key)
return value != null
return value != undefined
}
storeSettings(): Observable<any> {