Move mail settings to its own component
This commit is contained in:
parent
ff4e0e1d17
commit
da32877635
@ -2,24 +2,6 @@ import { test, expect } from '@playwright/test'
|
|||||||
|
|
||||||
const REQUESTS_HAR = 'e2e/admin/requests/api-settings.har'
|
const REQUESTS_HAR = 'e2e/admin/requests/api-settings.har'
|
||||||
|
|
||||||
test('should post settings on save', async ({ page }) => {
|
|
||||||
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
|
|
||||||
await page.goto('/settings')
|
|
||||||
await page.getByLabel('Use system setting').click()
|
|
||||||
await page.getByRole('button', { name: 'Save' }).scrollIntoViewIfNeeded()
|
|
||||||
const updatePromise = page.waitForRequest((request) => {
|
|
||||||
const data = request.postDataJSON()
|
|
||||||
const isValid = data['settings'] != null
|
|
||||||
return (
|
|
||||||
isValid &&
|
|
||||||
request.method() === 'POST' &&
|
|
||||||
request.url().includes('/api/ui_settings/')
|
|
||||||
)
|
|
||||||
})
|
|
||||||
await page.getByRole('button', { name: 'Save' }).click()
|
|
||||||
await updatePromise
|
|
||||||
})
|
|
||||||
|
|
||||||
test('should activate / deactivate save button when settings change', async ({
|
test('should activate / deactivate save button when settings change', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@ -89,11 +71,6 @@ test('should support tab direct navigation', async ({ page }) => {
|
|||||||
'aria-selected',
|
'aria-selected',
|
||||||
'true'
|
'true'
|
||||||
)
|
)
|
||||||
await page.goto('/settings/mail')
|
|
||||||
await expect(page.getByRole('tab', { name: 'Mail' })).toHaveAttribute(
|
|
||||||
'aria-selected',
|
|
||||||
'true'
|
|
||||||
)
|
|
||||||
await page.goto('/settings/usersgroups')
|
await page.goto('/settings/usersgroups')
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('tab', { name: 'Users & Groups' })
|
page.getByRole('tab', { name: 'Users & Groups' })
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
PermissionType,
|
PermissionType,
|
||||||
} from './services/permissions.service'
|
} from './services/permissions.service'
|
||||||
import { ConsumptionTemplatesListComponent } from './components/manage/consumption-templates-list/consumption-templates-list.component'
|
import { ConsumptionTemplatesListComponent } from './components/manage/consumption-templates-list/consumption-templates-list.component'
|
||||||
|
import { MailComponent } from './components/manage/mail/mail.component'
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||||
@ -143,6 +144,10 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'settings/mail',
|
||||||
|
redirectTo: '/mail',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
component: SettingsComponent,
|
component: SettingsComponent,
|
||||||
@ -167,11 +172,6 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'settings/:section',
|
|
||||||
component: SettingsComponent,
|
|
||||||
canDeactivate: [DirtyFormGuard],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'tasks',
|
path: 'tasks',
|
||||||
component: TasksComponent,
|
component: TasksComponent,
|
||||||
@ -194,6 +194,17 @@ export const routes: Routes = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'mail',
|
||||||
|
component: MailComponent,
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.MailAccount,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -179,6 +179,14 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
anchorId: 'tour.mail',
|
||||||
|
content: $localize`Manage e-mail accounts and rules for automatically importing documents.`,
|
||||||
|
route: '/mail',
|
||||||
|
backdropConfig: {
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
anchorId: 'tour.file-tasks',
|
anchorId: 'tour.file-tasks',
|
||||||
content: $localize`File Tasks shows you documents that have been consumed, are waiting to be, or may have failed during the process.`,
|
content: $localize`File Tasks shows you documents that have been consumed, are waiting to be, or may have failed during the process.`,
|
||||||
@ -189,7 +197,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
anchorId: 'tour.settings',
|
anchorId: 'tour.settings',
|
||||||
content: $localize`Check out the settings for various tweaks to the web app, toggle settings for saved views or setup e-mail checking.`,
|
content: $localize`Check out the settings for various tweaks to the web app, toggle settings for saved views or manage users.`,
|
||||||
route: '/settings',
|
route: '/settings',
|
||||||
backdropConfig: {
|
backdropConfig: {
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
@ -125,6 +125,7 @@ import localeSv from '@angular/common/locales/sv'
|
|||||||
import localeTr from '@angular/common/locales/tr'
|
import localeTr from '@angular/common/locales/tr'
|
||||||
import localeUk from '@angular/common/locales/uk'
|
import localeUk from '@angular/common/locales/uk'
|
||||||
import localeZh from '@angular/common/locales/zh'
|
import localeZh from '@angular/common/locales/zh'
|
||||||
|
import { MailComponent } from './components/manage/mail/mail.component'
|
||||||
|
|
||||||
registerLocaleData(localeAf)
|
registerLocaleData(localeAf)
|
||||||
registerLocaleData(localeAr)
|
registerLocaleData(localeAr)
|
||||||
@ -237,6 +238,7 @@ function initializeApp(settings: SettingsService) {
|
|||||||
ShareLinksDropdownComponent,
|
ShareLinksDropdownComponent,
|
||||||
ConsumptionTemplatesListComponent,
|
ConsumptionTemplatesListComponent,
|
||||||
ConsumptionTemplateEditDialogComponent,
|
ConsumptionTemplateEditDialogComponent,
|
||||||
|
MailComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<form [formGroup]="settingsForm" (ngSubmit)="saveSettings()">
|
<form [formGroup]="settingsForm" (ngSubmit)="saveSettings()">
|
||||||
|
|
||||||
<ul ngbNav #nav="ngbNav" (navChange)="onNavChange($event)" [(activeId)]="activeNavID" class="nav-tabs">
|
<ul ngbNav #nav="ngbNav" (navChange)="onNavChange($event)" [(activeId)]="activeNavID" class="nav-tabs">
|
||||||
<li [ngbNavItem]="SettingsNavIDs.General" (mouseover)="maybeInitializeTab(SettingsNavIDs.General)">
|
<li [ngbNavItem]="SettingsNavIDs.General">
|
||||||
<a ngbNavLink i18n>General</a>
|
<a ngbNavLink i18n>General</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
|
|
||||||
@ -261,7 +261,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li [ngbNavItem]="SettingsNavIDs.SavedViews" (mouseover)="maybeInitializeTab(SettingsNavIDs.SavedViews)" (focusin)="maybeInitializeTab(SettingsNavIDs.SavedViews)">
|
<li [ngbNavItem]="SettingsNavIDs.SavedViews">
|
||||||
<a ngbNavLink i18n>Saved views</a>
|
<a ngbNavLink i18n>Saved views</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
|
|
||||||
@ -311,97 +311,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailRule }" [ngbNavItem]="SettingsNavIDs.Mail" (mouseover)="maybeInitializeTab(SettingsNavIDs.Mail)" (focusin)="maybeInitializeTab(SettingsNavIDs.Mail)">
|
<li [ngbNavItem]="SettingsNavIDs.UsersGroups" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
|
||||||
<a ngbNavLink i18n>Mail</a>
|
|
||||||
<ng-template ngbNavContent>
|
|
||||||
|
|
||||||
<ng-container *ngIf="mailAccounts && mailRules">
|
|
||||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }">
|
|
||||||
<h4>
|
|
||||||
<ng-container i18n>Mail accounts</ng-container>
|
|
||||||
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailAccount()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailAccount }">
|
|
||||||
<svg class="sidebaricon me-1" fill="currentColor">
|
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
|
||||||
</svg>
|
|
||||||
<ng-container i18n>Add Account</ng-container>
|
|
||||||
</button>
|
|
||||||
</h4>
|
|
||||||
<ul class="list-group" formGroupName="mailAccounts">
|
|
||||||
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col" i18n>Name</div>
|
|
||||||
<div class="col" i18n>Server</div>
|
|
||||||
<div class="col" i18n>Actions</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li *ngFor="let account of mailAccounts" class="list-group-item" [formGroupName]="account.id">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount)">{{account.name}}</button></div>
|
|
||||||
<div class="col d-flex align-items-center">{{account.imap_server}}</div>
|
|
||||||
<div class="col">
|
|
||||||
<div class="btn-group">
|
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" [disabled]="!userCanEdit(account)" class="btn btn-sm btn-primary" type="button" (click)="editMailAccount(account)" i18n>Edit</button>
|
|
||||||
<button *pngxIfOwner="account" class="btn btn-sm btn-primary" type="button" (click)="editPermissions(account)" i18n>Permissions</button>
|
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailAccount }" [disabled]="!userIsOwner(account)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)" i18n>Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<div *ngIf="mailAccounts.length === 0" i18n>No mail accounts defined.</div>
|
|
||||||
</ul>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailRule }">
|
|
||||||
<h4 class="mt-4">
|
|
||||||
<ng-container i18n>Mail rules</ng-container>
|
|
||||||
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailRule()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailRule }">
|
|
||||||
<svg class="sidebaricon me-1" fill="currentColor">
|
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
|
||||||
</svg>
|
|
||||||
<ng-container i18n>Add Rule</ng-container>
|
|
||||||
</button>
|
|
||||||
</h4>
|
|
||||||
<ul class="list-group" formGroupName="mailRules">
|
|
||||||
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col" i18n>Name</div>
|
|
||||||
<div class="col" i18n>Account</div>
|
|
||||||
<div class="col" i18n>Actions</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li *ngFor="let rule of mailRules" class="list-group-item" [formGroupName]="rule.id">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule)">{{rule.name}}</button></div>
|
|
||||||
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
|
|
||||||
<div class="col">
|
|
||||||
<div class="btn-group">
|
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" [disabled]="!userCanEdit(rule)" class="btn btn-sm btn-primary" type="button" (click)="editMailRule(rule)" i18n>Edit</button>
|
|
||||||
<button *pngxIfOwner="rule" class="btn btn-sm btn-primary" type="button" (click)="editPermissions(rule)" i18n>Permissions</button>
|
|
||||||
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailRule }" [disabled]="!userIsOwner(rule)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)" i18n>Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<div *ngIf="mailRules.length === 0" i18n>No mail rules defined.</div>
|
|
||||||
</ul>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<div *ngIf="!mailAccounts || !mailRules">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<li [ngbNavItem]="SettingsNavIDs.UsersGroups" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }" (mouseover)="maybeInitializeTab(SettingsNavIDs.UsersGroups)" (focusin)="maybeInitializeTab(SettingsNavIDs.UsersGroups)">
|
|
||||||
<a ngbNavLink i18n>Users & Groups</a>
|
<a ngbNavLink i18n>Users & Groups</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
|
|
||||||
|
@ -20,8 +20,6 @@ import {
|
|||||||
import { NgSelectModule } from '@ng-select/ng-select'
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
import { of, throwError } from 'rxjs'
|
import { of, throwError } from 'rxjs'
|
||||||
import { routes } from 'src/app/app-routing.module'
|
import { routes } from 'src/app/app-routing.module'
|
||||||
import { PaperlessMailAccount } from 'src/app/data/paperless-mail-account'
|
|
||||||
import { PaperlessMailRule } from 'src/app/data/paperless-mail-rule'
|
|
||||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||||
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
|
||||||
@ -30,16 +28,12 @@ import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
|||||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
import { GroupService } from 'src/app/services/rest/group.service'
|
import { GroupService } from 'src/app/services/rest/group.service'
|
||||||
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
|
||||||
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
import { UserService } from 'src/app/services/rest/user.service'
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { ToastService, Toast } from 'src/app/services/toast.service'
|
import { ToastService, Toast } from 'src/app/services/toast.service'
|
||||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||||
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
|
||||||
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
|
||||||
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
||||||
import { CheckComponent } from '../../common/input/check/check.component'
|
import { CheckComponent } from '../../common/input/check/check.component'
|
||||||
import { ColorComponent } from '../../common/input/color/color.component'
|
import { ColorComponent } from '../../common/input/color/color.component'
|
||||||
@ -52,8 +46,6 @@ import { TagsComponent } from '../../common/input/tags/tags.component'
|
|||||||
import { TextComponent } from '../../common/input/text/text.component'
|
import { TextComponent } from '../../common/input/text/text.component'
|
||||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||||
import { SettingsComponent } from './settings.component'
|
import { SettingsComponent } from './settings.component'
|
||||||
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
|
|
||||||
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.component'
|
|
||||||
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
|
||||||
|
|
||||||
const savedViews = [
|
const savedViews = [
|
||||||
@ -68,14 +60,6 @@ const groups = [
|
|||||||
{ id: 1, name: 'group1' },
|
{ id: 1, name: 'group1' },
|
||||||
{ id: 2, name: 'group2' },
|
{ id: 2, name: 'group2' },
|
||||||
]
|
]
|
||||||
const mailAccounts = [
|
|
||||||
{ id: 1, name: 'account1' },
|
|
||||||
{ id: 2, name: 'account2' },
|
|
||||||
]
|
|
||||||
const mailRules = [
|
|
||||||
{ id: 1, name: 'rule1', owner: 1, account: 1 },
|
|
||||||
{ id: 2, name: 'rule2', owner: 2, account: 2 },
|
|
||||||
]
|
|
||||||
|
|
||||||
describe('SettingsComponent', () => {
|
describe('SettingsComponent', () => {
|
||||||
let component: SettingsComponent
|
let component: SettingsComponent
|
||||||
@ -90,8 +74,6 @@ describe('SettingsComponent', () => {
|
|||||||
let userService: UserService
|
let userService: UserService
|
||||||
let permissionsService: PermissionsService
|
let permissionsService: PermissionsService
|
||||||
let groupService: GroupService
|
let groupService: GroupService
|
||||||
let mailAccountService: MailAccountService
|
|
||||||
let mailRuleService: MailRuleService
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -109,13 +91,9 @@ describe('SettingsComponent', () => {
|
|||||||
PasswordComponent,
|
PasswordComponent,
|
||||||
NumberComponent,
|
NumberComponent,
|
||||||
TagsComponent,
|
TagsComponent,
|
||||||
MailAccountEditDialogComponent,
|
|
||||||
MailRuleEditDialogComponent,
|
|
||||||
PermissionsUserComponent,
|
PermissionsUserComponent,
|
||||||
PermissionsGroupComponent,
|
PermissionsGroupComponent,
|
||||||
IfOwnerDirective,
|
IfOwnerDirective,
|
||||||
PermissionsDialogComponent,
|
|
||||||
PermissionsFormComponent,
|
|
||||||
],
|
],
|
||||||
providers: [CustomDatePipe, DatePipe, PermissionsGuard],
|
providers: [CustomDatePipe, DatePipe, PermissionsGuard],
|
||||||
imports: [
|
imports: [
|
||||||
@ -135,7 +113,7 @@ describe('SettingsComponent', () => {
|
|||||||
viewportScroller = TestBed.inject(ViewportScroller)
|
viewportScroller = TestBed.inject(ViewportScroller)
|
||||||
toastService = TestBed.inject(ToastService)
|
toastService = TestBed.inject(ToastService)
|
||||||
settingsService = TestBed.inject(SettingsService)
|
settingsService = TestBed.inject(SettingsService)
|
||||||
settingsService.currentUser = { id: 99, username: 'user99' }
|
settingsService.currentUser = users[0]
|
||||||
userService = TestBed.inject(UserService)
|
userService = TestBed.inject(UserService)
|
||||||
permissionsService = TestBed.inject(PermissionsService)
|
permissionsService = TestBed.inject(PermissionsService)
|
||||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||||
@ -147,8 +125,6 @@ describe('SettingsComponent', () => {
|
|||||||
.mockReturnValue(true)
|
.mockReturnValue(true)
|
||||||
groupService = TestBed.inject(GroupService)
|
groupService = TestBed.inject(GroupService)
|
||||||
savedViewService = TestBed.inject(SavedViewService)
|
savedViewService = TestBed.inject(SavedViewService)
|
||||||
mailAccountService = TestBed.inject(MailAccountService)
|
|
||||||
mailRuleService = TestBed.inject(MailRuleService)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function completeSetup(excludeService = null) {
|
function completeSetup(excludeService = null) {
|
||||||
@ -179,24 +155,6 @@ describe('SettingsComponent', () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (excludeService !== mailAccountService) {
|
|
||||||
jest.spyOn(mailAccountService, 'listAll').mockReturnValue(
|
|
||||||
of({
|
|
||||||
all: mailAccounts.map((a) => a.id),
|
|
||||||
count: mailAccounts.length,
|
|
||||||
results: (mailAccounts as PaperlessMailAccount[]).concat([]),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (excludeService !== mailRuleService) {
|
|
||||||
jest.spyOn(mailRuleService, 'listAll').mockReturnValue(
|
|
||||||
of({
|
|
||||||
all: mailRules.map((r) => r.id),
|
|
||||||
count: mailRules.length,
|
|
||||||
results: (mailRules as PaperlessMailRule[]).concat([]),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(SettingsComponent)
|
fixture = TestBed.createComponent(SettingsComponent)
|
||||||
component = fixture.componentInstance
|
component = fixture.componentInstance
|
||||||
@ -212,8 +170,6 @@ describe('SettingsComponent', () => {
|
|||||||
tabButtons[2].nativeElement.dispatchEvent(new MouseEvent('click'))
|
tabButtons[2].nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||||
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'savedviews'])
|
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'savedviews'])
|
||||||
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click'))
|
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click'))
|
||||||
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'mail'])
|
|
||||||
tabButtons[4].nativeElement.dispatchEvent(new MouseEvent('click'))
|
|
||||||
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'usersgroups'])
|
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'usersgroups'])
|
||||||
|
|
||||||
const initSpy = jest.spyOn(component, 'initialize')
|
const initSpy = jest.spyOn(component, 'initialize')
|
||||||
@ -233,41 +189,17 @@ describe('SettingsComponent', () => {
|
|||||||
completeSetup()
|
completeSetup()
|
||||||
jest
|
jest
|
||||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||||
.mockReturnValue(of(convertToParamMap({ section: 'mail' })))
|
.mockReturnValue(of(convertToParamMap({ section: 'usersgroups' })))
|
||||||
activatedRoute.snapshot.fragment = '#mail'
|
activatedRoute.snapshot.fragment = '#usersgroups'
|
||||||
const scrollSpy = jest.spyOn(viewportScroller, 'scrollToAnchor')
|
const scrollSpy = jest.spyOn(viewportScroller, 'scrollToAnchor')
|
||||||
component.ngOnInit()
|
component.ngOnInit()
|
||||||
expect(component.activeNavID).toEqual(4) // Mail
|
expect(component.activeNavID).toEqual(4) // Users & Groups
|
||||||
component.ngAfterViewInit()
|
component.ngAfterViewInit()
|
||||||
expect(scrollSpy).toHaveBeenCalledWith('#mail')
|
expect(scrollSpy).toHaveBeenCalledWith('#usersgroups')
|
||||||
})
|
|
||||||
|
|
||||||
it('should lazy load tab data', () => {
|
|
||||||
completeSetup()
|
|
||||||
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
|
|
||||||
|
|
||||||
expect(component.savedViews).toBeUndefined()
|
|
||||||
tabButtons[2].nativeElement.dispatchEvent(
|
|
||||||
new MouseEvent('mouseover', { bubbles: true })
|
|
||||||
)
|
|
||||||
expect(component.savedViews).not.toBeUndefined()
|
|
||||||
|
|
||||||
expect(component.mailAccounts).toBeUndefined()
|
|
||||||
tabButtons[3].nativeElement.dispatchEvent(
|
|
||||||
new MouseEvent('mouseover', { bubbles: true })
|
|
||||||
)
|
|
||||||
expect(component.mailAccounts).not.toBeUndefined()
|
|
||||||
|
|
||||||
expect(component.groups).toBeUndefined()
|
|
||||||
tabButtons[4].nativeElement.dispatchEvent(
|
|
||||||
new MouseEvent('mouseover', { bubbles: true })
|
|
||||||
)
|
|
||||||
expect(component.groups).not.toBeUndefined()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support save saved views, show error', () => {
|
it('should support save saved views, show error', () => {
|
||||||
completeSetup()
|
completeSetup()
|
||||||
component.maybeInitializeTab(3) // SavedViews
|
|
||||||
|
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
const toastSpy = jest.spyOn(toastService, 'show')
|
const toastSpy = jest.spyOn(toastService, 'show')
|
||||||
@ -295,6 +227,7 @@ describe('SettingsComponent', () => {
|
|||||||
|
|
||||||
it('should support save local settings updating appearance settings and calling API, show error', () => {
|
it('should support save local settings updating appearance settings and calling API, show error', () => {
|
||||||
completeSetup()
|
completeSetup()
|
||||||
|
jest.spyOn(savedViewService, 'patchMany').mockReturnValue(of([]))
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
const toastSpy = jest.spyOn(toastService, 'show')
|
const toastSpy = jest.spyOn(toastService, 'show')
|
||||||
const storeSpy = jest.spyOn(settingsService, 'storeSettings')
|
const storeSpy = jest.spyOn(settingsService, 'storeSettings')
|
||||||
@ -309,6 +242,7 @@ describe('SettingsComponent', () => {
|
|||||||
throwError(() => new Error('unable to save settings'))
|
throwError(() => new Error('unable to save settings'))
|
||||||
)
|
)
|
||||||
component.saveSettings()
|
component.saveSettings()
|
||||||
|
|
||||||
expect(toastErrorSpy).toHaveBeenCalled()
|
expect(toastErrorSpy).toHaveBeenCalled()
|
||||||
expect(storeSpy).toHaveBeenCalled()
|
expect(storeSpy).toHaveBeenCalled()
|
||||||
expect(appearanceSettingsSpy).not.toHaveBeenCalled()
|
expect(appearanceSettingsSpy).not.toHaveBeenCalled()
|
||||||
@ -323,6 +257,7 @@ describe('SettingsComponent', () => {
|
|||||||
|
|
||||||
it('should offer reload if settings changes require', () => {
|
it('should offer reload if settings changes require', () => {
|
||||||
completeSetup()
|
completeSetup()
|
||||||
|
jest.spyOn(savedViewService, 'patchMany').mockReturnValue(of([]))
|
||||||
let toast: Toast
|
let toast: Toast
|
||||||
toastService.getToasts().subscribe((t) => (toast = t[0]))
|
toastService.getToasts().subscribe((t) => (toast = t[0]))
|
||||||
component.initialize(true) // reset
|
component.initialize(true) // reset
|
||||||
@ -354,7 +289,6 @@ describe('SettingsComponent', () => {
|
|||||||
|
|
||||||
it('should support delete saved view', () => {
|
it('should support delete saved view', () => {
|
||||||
completeSetup()
|
completeSetup()
|
||||||
component.maybeInitializeTab(3) // SavedViews
|
|
||||||
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
const toastSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
const deleteSpy = jest.spyOn(savedViewService, 'delete')
|
const deleteSpy = jest.spyOn(savedViewService, 'delete')
|
||||||
deleteSpy.mockReturnValue(of(true))
|
deleteSpy.mockReturnValue(of(true))
|
||||||
@ -462,40 +396,10 @@ describe('SettingsComponent', () => {
|
|||||||
|
|
||||||
it('should get group name', () => {
|
it('should get group name', () => {
|
||||||
completeSetup()
|
completeSetup()
|
||||||
component.maybeInitializeTab(5) // UsersGroups
|
|
||||||
expect(component.getGroupName(1)).toEqual(groups[0].name)
|
expect(component.getGroupName(1)).toEqual(groups[0].name)
|
||||||
expect(component.getGroupName(11)).toEqual('')
|
expect(component.getGroupName(11)).toEqual('')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show errors on load if load mailAccounts failure', () => {
|
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
|
||||||
jest
|
|
||||||
.spyOn(mailAccountService, 'listAll')
|
|
||||||
.mockImplementation(() =>
|
|
||||||
throwError(() => new Error('failed to load mail accounts'))
|
|
||||||
)
|
|
||||||
completeSetup(mailAccountService)
|
|
||||||
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
|
|
||||||
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) // mail tab
|
|
||||||
fixture.detectChanges()
|
|
||||||
expect(toastErrorSpy).toBeCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show errors on load if load mailRules failure', () => {
|
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
|
||||||
jest
|
|
||||||
.spyOn(mailRuleService, 'listAll')
|
|
||||||
.mockImplementation(() =>
|
|
||||||
throwError(() => new Error('failed to load mail rules'))
|
|
||||||
)
|
|
||||||
completeSetup(mailRuleService)
|
|
||||||
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
|
|
||||||
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) // mail tab
|
|
||||||
fixture.detectChanges()
|
|
||||||
// tabButtons[4].nativeElement.dispatchEvent(new MouseEvent('click'))
|
|
||||||
expect(toastErrorSpy).toBeCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show errors on load if load users failure', () => {
|
it('should show errors on load if load users failure', () => {
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
jest
|
jest
|
||||||
@ -505,7 +409,7 @@ describe('SettingsComponent', () => {
|
|||||||
)
|
)
|
||||||
completeSetup(userService)
|
completeSetup(userService)
|
||||||
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
|
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
|
||||||
tabButtons[4].nativeElement.dispatchEvent(new MouseEvent('click')) // users tab
|
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) // users tab
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(toastErrorSpy).toBeCalled()
|
expect(toastErrorSpy).toBeCalled()
|
||||||
})
|
})
|
||||||
@ -519,147 +423,8 @@ describe('SettingsComponent', () => {
|
|||||||
)
|
)
|
||||||
completeSetup(groupService)
|
completeSetup(groupService)
|
||||||
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
|
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
|
||||||
tabButtons[4].nativeElement.dispatchEvent(new MouseEvent('click')) // users tab
|
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) // users tab
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(toastErrorSpy).toBeCalled()
|
expect(toastErrorSpy).toBeCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support edit / create mail account, show error if needed', () => {
|
|
||||||
completeSetup()
|
|
||||||
let modal: NgbModalRef
|
|
||||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
|
||||||
component.editMailAccount(mailAccounts[0] as PaperlessMailAccount)
|
|
||||||
const editDialog = modal.componentInstance as MailAccountEditDialogComponent
|
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
|
||||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
|
||||||
editDialog.failed.emit()
|
|
||||||
expect(toastErrorSpy).toBeCalled()
|
|
||||||
editDialog.succeeded.emit(mailAccounts[0])
|
|
||||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
|
||||||
`Saved account "${mailAccounts[0].name}".`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support delete mail account, show error if needed', () => {
|
|
||||||
completeSetup()
|
|
||||||
let modal: NgbModalRef
|
|
||||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
|
||||||
component.deleteMailAccount(mailAccounts[0] as PaperlessMailAccount)
|
|
||||||
const deleteDialog = modal.componentInstance as ConfirmDialogComponent
|
|
||||||
const deleteSpy = jest.spyOn(mailAccountService, 'delete')
|
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
|
||||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
|
||||||
const listAllSpy = jest.spyOn(mailAccountService, 'listAll')
|
|
||||||
deleteSpy.mockReturnValueOnce(
|
|
||||||
throwError(() => new Error('error deleting mail account'))
|
|
||||||
)
|
|
||||||
deleteDialog.confirm()
|
|
||||||
expect(toastErrorSpy).toBeCalled()
|
|
||||||
deleteSpy.mockReturnValueOnce(of(true))
|
|
||||||
deleteDialog.confirm()
|
|
||||||
expect(listAllSpy).toHaveBeenCalled()
|
|
||||||
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail account')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support edit / create mail rule, show error if needed', () => {
|
|
||||||
completeSetup()
|
|
||||||
let modal: NgbModalRef
|
|
||||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
|
||||||
component.editMailRule(mailRules[0] as PaperlessMailRule)
|
|
||||||
const editDialog = modal.componentInstance as MailRuleEditDialogComponent
|
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
|
||||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
|
||||||
editDialog.failed.emit()
|
|
||||||
expect(toastErrorSpy).toBeCalled()
|
|
||||||
editDialog.succeeded.emit(mailRules[0])
|
|
||||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
|
||||||
`Saved rule "${mailRules[0].name}".`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support delete mail rule, show error if needed', () => {
|
|
||||||
completeSetup()
|
|
||||||
let modal: NgbModalRef
|
|
||||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
|
||||||
component.deleteMailRule(mailRules[0] as PaperlessMailRule)
|
|
||||||
const deleteDialog = modal.componentInstance as ConfirmDialogComponent
|
|
||||||
const deleteSpy = jest.spyOn(mailRuleService, 'delete')
|
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
|
||||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
|
||||||
const listAllSpy = jest.spyOn(mailRuleService, 'listAll')
|
|
||||||
deleteSpy.mockReturnValueOnce(
|
|
||||||
throwError(() => new Error('error deleting mail rule'))
|
|
||||||
)
|
|
||||||
deleteDialog.confirm()
|
|
||||||
expect(toastErrorSpy).toBeCalled()
|
|
||||||
deleteSpy.mockReturnValueOnce(of(true))
|
|
||||||
deleteDialog.confirm()
|
|
||||||
expect(listAllSpy).toHaveBeenCalled()
|
|
||||||
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support edit permissions on mail rule objects', () => {
|
|
||||||
completeSetup()
|
|
||||||
const perms = {
|
|
||||||
owner: 99,
|
|
||||||
set_permissions: {
|
|
||||||
view: {
|
|
||||||
users: [1],
|
|
||||||
groups: [2],
|
|
||||||
},
|
|
||||||
change: {
|
|
||||||
users: [3],
|
|
||||||
groups: [4],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
let modal: NgbModalRef
|
|
||||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
|
||||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
|
||||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
|
||||||
const rulePatchSpy = jest.spyOn(mailRuleService, 'patch')
|
|
||||||
component.editPermissions(mailRules[0] as PaperlessMailRule)
|
|
||||||
expect(modal).not.toBeUndefined()
|
|
||||||
let dialog = modal.componentInstance as PermissionsDialogComponent
|
|
||||||
expect(dialog.object).toEqual(mailRules[0])
|
|
||||||
|
|
||||||
rulePatchSpy.mockReturnValueOnce(
|
|
||||||
throwError(() => new Error('error saving perms'))
|
|
||||||
)
|
|
||||||
dialog.confirmClicked.emit(perms)
|
|
||||||
expect(rulePatchSpy).toHaveBeenCalled()
|
|
||||||
expect(toastErrorSpy).toHaveBeenCalled()
|
|
||||||
rulePatchSpy.mockReturnValueOnce(of(mailRules[0] as PaperlessMailRule))
|
|
||||||
dialog.confirmClicked.emit(perms)
|
|
||||||
expect(toastInfoSpy).toHaveBeenCalledWith('Permissions updated')
|
|
||||||
|
|
||||||
modalService.dismissAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should support edit permissions on mail account objects', () => {
|
|
||||||
completeSetup()
|
|
||||||
const perms = {
|
|
||||||
owner: 99,
|
|
||||||
set_permissions: {
|
|
||||||
view: {
|
|
||||||
users: [1],
|
|
||||||
groups: [2],
|
|
||||||
},
|
|
||||||
change: {
|
|
||||||
users: [3],
|
|
||||||
groups: [4],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
let modal: NgbModalRef
|
|
||||||
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
|
||||||
const accountPatchSpy = jest.spyOn(mailAccountService, 'patch')
|
|
||||||
component.editPermissions(mailAccounts[0] as PaperlessMailAccount)
|
|
||||||
expect(modal).not.toBeUndefined()
|
|
||||||
let dialog = modal.componentInstance as PermissionsDialogComponent
|
|
||||||
expect(dialog.object).toEqual(mailAccounts[0])
|
|
||||||
dialog = modal.componentInstance as PermissionsDialogComponent
|
|
||||||
dialog.confirmClicked.emit(perms)
|
|
||||||
expect(accountPatchSpy).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
@ -1,65 +1,56 @@
|
|||||||
|
import { ViewportScroller } from '@angular/common'
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
|
OnInit,
|
||||||
|
AfterViewInit,
|
||||||
|
OnDestroy,
|
||||||
Inject,
|
Inject,
|
||||||
LOCALE_ID,
|
LOCALE_ID,
|
||||||
OnInit,
|
|
||||||
OnDestroy,
|
|
||||||
AfterViewInit,
|
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormGroup, FormControl } from '@angular/forms'
|
||||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
|
||||||
import {
|
|
||||||
LanguageOption,
|
|
||||||
SettingsService,
|
|
||||||
} from 'src/app/services/settings.service'
|
|
||||||
import { Toast, ToastService } from 'src/app/services/toast.service'
|
|
||||||
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
|
|
||||||
import {
|
|
||||||
Observable,
|
|
||||||
Subscription,
|
|
||||||
BehaviorSubject,
|
|
||||||
first,
|
|
||||||
tap,
|
|
||||||
takeUntil,
|
|
||||||
Subject,
|
|
||||||
} from 'rxjs'
|
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } 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 { NgbModal, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbModal, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { UserService } from 'src/app/services/rest/user.service'
|
import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms'
|
||||||
import { GroupService } from 'src/app/services/rest/group.service'
|
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
||||||
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'
|
|
||||||
import { PaperlessMailAccount } from 'src/app/data/paperless-mail-account'
|
|
||||||
import { PaperlessMailRule } from 'src/app/data/paperless-mail-rule'
|
|
||||||
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
|
||||||
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
|
||||||
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
|
||||||
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
|
||||||
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
|
||||||
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
|
|
||||||
import {
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
Subscription,
|
||||||
|
Observable,
|
||||||
|
Subject,
|
||||||
|
first,
|
||||||
|
takeUntil,
|
||||||
|
tap,
|
||||||
|
} from 'rxjs'
|
||||||
|
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
|
||||||
|
import { PaperlessGroup } from 'src/app/data/paperless-group'
|
||||||
|
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
||||||
|
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||||
|
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||||
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
|
import {
|
||||||
|
PermissionsService,
|
||||||
PermissionAction,
|
PermissionAction,
|
||||||
PermissionType,
|
PermissionType,
|
||||||
PermissionsService,
|
|
||||||
} from 'src/app/services/permissions.service'
|
} from 'src/app/services/permissions.service'
|
||||||
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
|
import { GroupService } from 'src/app/services/rest/group.service'
|
||||||
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
import {
|
||||||
|
SettingsService,
|
||||||
|
LanguageOption,
|
||||||
|
} from 'src/app/services/settings.service'
|
||||||
|
import { ToastService, Toast } from 'src/app/services/toast.service'
|
||||||
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
|
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||||
|
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||||
|
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
||||||
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
enum SettingsNavIDs {
|
enum SettingsNavIDs {
|
||||||
General = 1,
|
General = 1,
|
||||||
Notifications = 2,
|
Notifications = 2,
|
||||||
SavedViews = 3,
|
SavedViews = 3,
|
||||||
Mail = 4,
|
UsersGroups = 4,
|
||||||
UsersGroups = 5,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -78,9 +69,6 @@ export class SettingsComponent
|
|||||||
usersGroup = new FormGroup({})
|
usersGroup = new FormGroup({})
|
||||||
groupsGroup = new FormGroup({})
|
groupsGroup = new FormGroup({})
|
||||||
|
|
||||||
mailAccountGroup = new FormGroup({})
|
|
||||||
mailRuleGroup = new FormGroup({})
|
|
||||||
|
|
||||||
settingsForm = new FormGroup({
|
settingsForm = new FormGroup({
|
||||||
bulkEditConfirmationDialogs: new FormControl(null),
|
bulkEditConfirmationDialogs: new FormControl(null),
|
||||||
bulkEditApplyOnClose: new FormControl(null),
|
bulkEditApplyOnClose: new FormControl(null),
|
||||||
@ -111,16 +99,10 @@ export class SettingsComponent
|
|||||||
|
|
||||||
savedViewsWarnOnUnsavedChange: new FormControl(null),
|
savedViewsWarnOnUnsavedChange: new FormControl(null),
|
||||||
savedViews: this.savedViewGroup,
|
savedViews: this.savedViewGroup,
|
||||||
|
|
||||||
mailAccounts: this.mailAccountGroup,
|
|
||||||
mailRules: this.mailRuleGroup,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
savedViews: PaperlessSavedView[]
|
savedViews: PaperlessSavedView[]
|
||||||
|
|
||||||
mailAccounts: PaperlessMailAccount[]
|
|
||||||
mailRules: PaperlessMailRule[]
|
|
||||||
|
|
||||||
store: BehaviorSubject<any>
|
store: BehaviorSubject<any>
|
||||||
storeSub: Subscription
|
storeSub: Subscription
|
||||||
isDirty$: Observable<boolean>
|
isDirty$: Observable<boolean>
|
||||||
@ -141,8 +123,6 @@ export class SettingsComponent
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public savedViewService: SavedViewService,
|
public savedViewService: SavedViewService,
|
||||||
public mailAccountService: MailAccountService,
|
|
||||||
public mailRuleService: MailRuleService,
|
|
||||||
private documentListViewService: DocumentListViewService,
|
private documentListViewService: DocumentListViewService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private settings: SettingsService,
|
private settings: SettingsService,
|
||||||
@ -165,25 +145,66 @@ export class SettingsComponent
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.initialize()
|
this.initialize()
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.permissionsService.currentUserCan(
|
||||||
|
PermissionAction.View,
|
||||||
|
PermissionType.User
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.usersService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe({
|
||||||
|
next: (r) => {
|
||||||
|
this.users = r.results
|
||||||
|
this.initialize(false)
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError($localize`Error retrieving users`, e)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.permissionsService.currentUserCan(
|
||||||
|
PermissionAction.View,
|
||||||
|
PermissionType.Group
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.permissionsService.currentUserCan(
|
||||||
|
PermissionAction.View,
|
||||||
|
PermissionType.SavedView
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.savedViewService.listAll().subscribe((r) => {
|
||||||
|
this.savedViews = r.results
|
||||||
|
this.initialize(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
this.activatedRoute.paramMap.subscribe((paramMap) => {
|
this.activatedRoute.paramMap.subscribe((paramMap) => {
|
||||||
const section = paramMap.get('section')
|
const section = paramMap.get('section')
|
||||||
if (section === null) {
|
|
||||||
if (
|
|
||||||
this.permissionsService.currentUserCan(
|
|
||||||
PermissionAction.View,
|
|
||||||
PermissionType.User
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this.getUsers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (section) {
|
if (section) {
|
||||||
const navIDKey: string = Object.keys(SettingsNavIDs).find(
|
const navIDKey: string = Object.keys(SettingsNavIDs).find(
|
||||||
(navID) => navID.toLowerCase() == section
|
(navID) => navID.toLowerCase() == section
|
||||||
)
|
)
|
||||||
if (navIDKey) {
|
if (navIDKey) {
|
||||||
this.activeNavID = SettingsNavIDs[navIDKey]
|
this.activeNavID = SettingsNavIDs[navIDKey]
|
||||||
this.maybeInitializeTab(this.activeNavID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -256,13 +277,10 @@ export class SettingsComponent
|
|||||||
usersGroup: {},
|
usersGroup: {},
|
||||||
groupsGroup: {},
|
groupsGroup: {},
|
||||||
savedViews: {},
|
savedViews: {},
|
||||||
mailAccounts: {},
|
|
||||||
mailRules: {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onNavChange(navChangeEvent: NgbNavChangeEvent) {
|
onNavChange(navChangeEvent: NgbNavChangeEvent) {
|
||||||
this.maybeInitializeTab(navChangeEvent.nextId)
|
|
||||||
const [foundNavIDkey] = Object.entries(SettingsNavIDs).find(
|
const [foundNavIDkey] = Object.entries(SettingsNavIDs).find(
|
||||||
([, navIDValue]) => navIDValue == navChangeEvent.nextId
|
([, navIDValue]) => navIDValue == navChangeEvent.nextId
|
||||||
)
|
)
|
||||||
@ -279,83 +297,6 @@ export class SettingsComponent
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load tab contents 'on demand', either on mouseover or focusin (i.e. before click) or called from nav change event
|
|
||||||
maybeInitializeTab(navID: number): void {
|
|
||||||
if (navID == SettingsNavIDs.SavedViews && !this.savedViews) {
|
|
||||||
this.savedViewService.listAll().subscribe((r) => {
|
|
||||||
this.savedViews = r.results
|
|
||||||
this.initialize(false)
|
|
||||||
})
|
|
||||||
} else if (
|
|
||||||
(navID == SettingsNavIDs.UsersGroups ||
|
|
||||||
navID == SettingsNavIDs.General) &&
|
|
||||||
(!this.users || !this.groups)
|
|
||||||
) {
|
|
||||||
if (!this.users) this.getUsers()
|
|
||||||
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)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} else if (
|
|
||||||
navID == SettingsNavIDs.Mail &&
|
|
||||||
(!this.mailAccounts || !this.mailRules)
|
|
||||||
) {
|
|
||||||
this.mailAccountService
|
|
||||||
.listAll(null, null, { full_perms: true })
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe({
|
|
||||||
next: (r) => {
|
|
||||||
this.mailAccounts = r.results
|
|
||||||
|
|
||||||
this.mailRuleService
|
|
||||||
.listAll(null, null, { full_perms: true })
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe({
|
|
||||||
next: (r) => {
|
|
||||||
this.mailRules = r.results
|
|
||||||
this.initialize(false)
|
|
||||||
},
|
|
||||||
error: (e) => {
|
|
||||||
this.toastService.showError(
|
|
||||||
$localize`Error retrieving mail rules`,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
error: (e) => {
|
|
||||||
this.toastService.showError(
|
|
||||||
$localize`Error retrieving mail accounts`,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getUsers() {
|
|
||||||
this.usersService
|
|
||||||
.listAll()
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe({
|
|
||||||
next: (r) => {
|
|
||||||
this.users = r.results
|
|
||||||
this.initialize(false)
|
|
||||||
},
|
|
||||||
error: (e) => {
|
|
||||||
this.toastService.showError($localize`Error retrieving users`, e)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize(resetSettings: boolean = true) {
|
initialize(resetSettings: boolean = true) {
|
||||||
this.unsubscribeNotifier.next(true)
|
this.unsubscribeNotifier.next(true)
|
||||||
|
|
||||||
@ -414,6 +355,7 @@ export class SettingsComponent
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.groups) {
|
if (this.groups) {
|
||||||
this.emptyGroup(this.groupsGroup)
|
this.emptyGroup(this.groupsGroup)
|
||||||
for (let group of this.groups) {
|
for (let group of this.groups) {
|
||||||
@ -433,81 +375,6 @@ export class SettingsComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.mailAccounts && this.mailRules) {
|
|
||||||
this.emptyGroup(this.mailAccountGroup)
|
|
||||||
this.emptyGroup(this.mailRuleGroup)
|
|
||||||
|
|
||||||
for (let account of this.mailAccounts) {
|
|
||||||
storeData.mailAccounts[account.id.toString()] = {
|
|
||||||
id: account.id,
|
|
||||||
name: account.name,
|
|
||||||
imap_server: account.imap_server,
|
|
||||||
imap_port: account.imap_port,
|
|
||||||
imap_security: account.imap_security,
|
|
||||||
username: account.username,
|
|
||||||
password: account.password,
|
|
||||||
character_set: account.character_set,
|
|
||||||
}
|
|
||||||
this.mailAccountGroup.addControl(
|
|
||||||
account.id.toString(),
|
|
||||||
new FormGroup({
|
|
||||||
id: new FormControl(null),
|
|
||||||
name: new FormControl(null),
|
|
||||||
imap_server: new FormControl(null),
|
|
||||||
imap_port: new FormControl(null),
|
|
||||||
imap_security: new FormControl(null),
|
|
||||||
username: new FormControl(null),
|
|
||||||
password: new FormControl(null),
|
|
||||||
character_set: new FormControl(null),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let rule of this.mailRules) {
|
|
||||||
storeData.mailRules[rule.id.toString()] = {
|
|
||||||
name: rule.name,
|
|
||||||
account: rule.account,
|
|
||||||
folder: rule.folder,
|
|
||||||
filter_from: rule.filter_from,
|
|
||||||
filter_to: rule.filter_to,
|
|
||||||
filter_subject: rule.filter_subject,
|
|
||||||
filter_body: rule.filter_body,
|
|
||||||
filter_attachment_filename: rule.filter_attachment_filename,
|
|
||||||
maximum_age: rule.maximum_age,
|
|
||||||
attachment_type: rule.attachment_type,
|
|
||||||
action: rule.action,
|
|
||||||
action_parameter: rule.action_parameter,
|
|
||||||
assign_title_from: rule.assign_title_from,
|
|
||||||
assign_tags: rule.assign_tags,
|
|
||||||
assign_document_type: rule.assign_document_type,
|
|
||||||
assign_correspondent_from: rule.assign_correspondent_from,
|
|
||||||
assign_correspondent: rule.assign_correspondent,
|
|
||||||
}
|
|
||||||
this.mailRuleGroup.addControl(
|
|
||||||
rule.id.toString(),
|
|
||||||
new FormGroup({
|
|
||||||
name: new FormControl(null),
|
|
||||||
account: new FormControl(null),
|
|
||||||
folder: new FormControl(null),
|
|
||||||
filter_from: new FormControl(null),
|
|
||||||
filter_to: new FormControl(null),
|
|
||||||
filter_subject: new FormControl(null),
|
|
||||||
filter_body: new FormControl(null),
|
|
||||||
filter_attachment_filename: new FormControl(null),
|
|
||||||
maximum_age: new FormControl(null),
|
|
||||||
attachment_type: new FormControl(null),
|
|
||||||
action: new FormControl(null),
|
|
||||||
action_parameter: new FormControl(null),
|
|
||||||
assign_title_from: new FormControl(null),
|
|
||||||
assign_tags: new FormControl(null),
|
|
||||||
assign_document_type: new FormControl(null),
|
|
||||||
assign_correspondent_from: new FormControl(null),
|
|
||||||
assign_correspondent: 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) => {
|
||||||
@ -722,17 +589,17 @@ export class SettingsComponent
|
|||||||
x.push(this.savedViewGroup.value[id])
|
x.push(this.savedViewGroup.value[id])
|
||||||
}
|
}
|
||||||
if (x.length > 0) {
|
if (x.length > 0) {
|
||||||
this.savedViewService.patchMany(x).subscribe(
|
this.savedViewService.patchMany(x).subscribe({
|
||||||
(s) => {
|
next: () => {
|
||||||
this.saveLocalSettings()
|
this.saveLocalSettings()
|
||||||
},
|
},
|
||||||
(error) => {
|
error: (error) => {
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Error while storing settings on server.`,
|
$localize`Error while storing settings on server.`,
|
||||||
error
|
error
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
)
|
})
|
||||||
} else {
|
} else {
|
||||||
this.saveLocalSettings()
|
this.saveLocalSettings()
|
||||||
}
|
}
|
||||||
@ -874,152 +741,4 @@ export class SettingsComponent
|
|||||||
getGroupName(id: number): string {
|
getGroupName(id: number): string {
|
||||||
return this.groups?.find((g) => g.id === id)?.name ?? ''
|
return this.groups?.find((g) => g.id === id)?.name ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
editMailAccount(account: PaperlessMailAccount) {
|
|
||||||
const modal = this.modalService.open(MailAccountEditDialogComponent, {
|
|
||||||
backdrop: 'static',
|
|
||||||
size: 'xl',
|
|
||||||
})
|
|
||||||
modal.componentInstance.dialogMode = account
|
|
||||||
? EditDialogMode.EDIT
|
|
||||||
: EditDialogMode.CREATE
|
|
||||||
modal.componentInstance.object = account
|
|
||||||
modal.componentInstance.succeeded
|
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
|
||||||
.subscribe((newMailAccount) => {
|
|
||||||
this.toastService.showInfo(
|
|
||||||
$localize`Saved account "${newMailAccount.name}".`
|
|
||||||
)
|
|
||||||
this.mailAccountService.clearCache()
|
|
||||||
this.mailAccountService
|
|
||||||
.listAll(null, null, { full_perms: true })
|
|
||||||
.subscribe((r) => {
|
|
||||||
this.mailAccounts = r.results
|
|
||||||
this.initialize()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
modal.componentInstance.failed
|
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
|
||||||
.subscribe((e) => {
|
|
||||||
this.toastService.showError($localize`Error saving account.`, e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteMailAccount(account: PaperlessMailAccount) {
|
|
||||||
const modal = this.modalService.open(ConfirmDialogComponent, {
|
|
||||||
backdrop: 'static',
|
|
||||||
})
|
|
||||||
modal.componentInstance.title = $localize`Confirm delete mail account`
|
|
||||||
modal.componentInstance.messageBold = $localize`This operation will permanently delete this mail 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.mailAccountService.delete(account).subscribe({
|
|
||||||
next: () => {
|
|
||||||
modal.close()
|
|
||||||
this.toastService.showInfo($localize`Deleted mail account`)
|
|
||||||
this.mailAccountService.clearCache()
|
|
||||||
this.mailAccountService
|
|
||||||
.listAll(null, null, { full_perms: true })
|
|
||||||
.subscribe((r) => {
|
|
||||||
this.mailAccounts = r.results
|
|
||||||
this.initialize(true)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
error: (e) => {
|
|
||||||
this.toastService.showError(
|
|
||||||
$localize`Error deleting mail account.`,
|
|
||||||
e
|
|
||||||
)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
editMailRule(rule: PaperlessMailRule) {
|
|
||||||
const modal = this.modalService.open(MailRuleEditDialogComponent, {
|
|
||||||
backdrop: 'static',
|
|
||||||
size: 'xl',
|
|
||||||
})
|
|
||||||
modal.componentInstance.dialogMode = rule
|
|
||||||
? EditDialogMode.EDIT
|
|
||||||
: EditDialogMode.CREATE
|
|
||||||
modal.componentInstance.object = rule
|
|
||||||
modal.componentInstance.succeeded
|
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
|
||||||
.subscribe((newMailRule) => {
|
|
||||||
this.toastService.showInfo($localize`Saved rule "${newMailRule.name}".`)
|
|
||||||
this.mailRuleService.clearCache()
|
|
||||||
this.mailRuleService
|
|
||||||
.listAll(null, null, { full_perms: true })
|
|
||||||
.subscribe((r) => {
|
|
||||||
this.mailRules = r.results
|
|
||||||
|
|
||||||
this.initialize(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
modal.componentInstance.failed
|
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
|
||||||
.subscribe((e) => {
|
|
||||||
this.toastService.showError($localize`Error saving rule.`, e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteMailRule(rule: PaperlessMailRule) {
|
|
||||||
const modal = this.modalService.open(ConfirmDialogComponent, {
|
|
||||||
backdrop: 'static',
|
|
||||||
})
|
|
||||||
modal.componentInstance.title = $localize`Confirm delete mail rule`
|
|
||||||
modal.componentInstance.messageBold = $localize`This operation will permanently delete this mail rule.`
|
|
||||||
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.mailRuleService.delete(rule).subscribe({
|
|
||||||
next: () => {
|
|
||||||
modal.close()
|
|
||||||
this.toastService.showInfo($localize`Deleted mail rule`)
|
|
||||||
this.mailRuleService.clearCache()
|
|
||||||
this.mailRuleService
|
|
||||||
.listAll(null, null, { full_perms: true })
|
|
||||||
.subscribe((r) => {
|
|
||||||
this.mailRules = r.results
|
|
||||||
this.initialize(true)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
error: (e) => {
|
|
||||||
this.toastService.showError($localize`Error deleting mail rule.`, e)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
editPermissions(object: PaperlessMailRule | PaperlessMailAccount) {
|
|
||||||
const modal = this.modalService.open(PermissionsDialogComponent, {
|
|
||||||
backdrop: 'static',
|
|
||||||
})
|
|
||||||
const dialog: PermissionsDialogComponent =
|
|
||||||
modal.componentInstance as PermissionsDialogComponent
|
|
||||||
dialog.object = object
|
|
||||||
modal.componentInstance.confirmClicked.subscribe((permissions) => {
|
|
||||||
modal.componentInstance.buttonsEnabled = false
|
|
||||||
const service: AbstractPaperlessService<
|
|
||||||
PaperlessMailRule | PaperlessMailAccount
|
|
||||||
> = 'account' in object ? this.mailRuleService : this.mailAccountService
|
|
||||||
object.owner = permissions['owner']
|
|
||||||
object['set_permissions'] = permissions['set_permissions']
|
|
||||||
service.patch(object).subscribe({
|
|
||||||
next: () => {
|
|
||||||
this.toastService.showInfo($localize`Permissions updated`)
|
|
||||||
modal.close()
|
|
||||||
},
|
|
||||||
error: (e) => {
|
|
||||||
this.toastService.showError($localize`Error updating permissions`, e)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -162,12 +162,26 @@
|
|||||||
</svg><span> <ng-container i18n>Templates</ng-container></span>
|
</svg><span> <ng-container i18n>Templates</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }" tourAnchor="tour.mail">
|
||||||
|
<a class="nav-link" routerLink="mail" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Mail" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#envelope"/>
|
||||||
|
</svg><span> <ng-container i18n>Mail</ng-container></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted">
|
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted">
|
||||||
<span i18n>Administration</span>
|
<span i18n>Administration</span>
|
||||||
</h6>
|
</h6>
|
||||||
<ul class="nav flex-column mb-2">
|
<ul class="nav flex-column mb-2">
|
||||||
|
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }" tourAnchor="tour.settings">
|
||||||
|
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||||
|
</svg><span> <ng-container i18n>Settings</ng-container></span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.PaperlessTask }" tourAnchor="tour.file-tasks">
|
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.PaperlessTask }" tourAnchor="tour.file-tasks">
|
||||||
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()" ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()" ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<span *ngIf="tasksService.failedFileTasks.length > 0 && slimSidebarEnabled" class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>
|
<span *ngIf="tasksService.failedFileTasks.length > 0 && slimSidebarEnabled" class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>
|
||||||
@ -183,13 +197,6 @@
|
|||||||
</svg><span> <ng-container i18n>Logs</ng-container></span>
|
</svg><span> <ng-container i18n>Logs</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }" tourAnchor="tour.settings">
|
|
||||||
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
|
||||||
</svg><span> <ng-container i18n>Settings</ng-container></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h6 class="sidebar-heading px-3 mt-auto pt-4 mb-1 text-muted">
|
<h6 class="sidebar-heading px-3 mt-auto pt-4 mb-1 text-muted">
|
||||||
|
83
src-ui/src/app/components/manage/mail/mail.component.html
Normal file
83
src-ui/src/app/components/manage/mail/mail.component.html
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<pngx-page-header title="Mail Settings" i18n-title>
|
||||||
|
</pngx-page-header>
|
||||||
|
|
||||||
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }">
|
||||||
|
<h4>
|
||||||
|
<ng-container i18n>Mail accounts</ng-container>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailAccount()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailAccount }">
|
||||||
|
<svg class="sidebaricon me-1" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||||
|
</svg>
|
||||||
|
<ng-container i18n>Add Account</ng-container>
|
||||||
|
</button>
|
||||||
|
</h4>
|
||||||
|
<ul class="list-group">
|
||||||
|
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col" i18n>Name</div>
|
||||||
|
<div class="col" i18n>Server</div>
|
||||||
|
<div class="col" i18n>Actions</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li *ngFor="let account of mailAccounts" class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailAccount(account)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailAccount)">{{account.name}}</button></div>
|
||||||
|
<div class="col d-flex align-items-center">{{account.imap_server}}</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" [disabled]="!userCanEdit(account)" class="btn btn-sm btn-primary" type="button" (click)="editMailAccount(account)" i18n>Edit</button>
|
||||||
|
<button *pngxIfOwner="account" class="btn btn-sm btn-primary" type="button" (click)="editPermissions(account)" i18n>Permissions</button>
|
||||||
|
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailAccount }" [disabled]="!userIsOwner(account)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)" i18n>Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<div *ngIf="mailAccounts.length === 0" i18n>No mail accounts defined.</div>
|
||||||
|
</ul>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailRule }">
|
||||||
|
<h4 class="mt-4">
|
||||||
|
<ng-container i18n>Mail rules</ng-container>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailRule()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.MailRule }">
|
||||||
|
<svg class="sidebaricon me-1" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||||
|
</svg>
|
||||||
|
<ng-container i18n>Add Rule</ng-container>
|
||||||
|
</button>
|
||||||
|
</h4>
|
||||||
|
<ul class="list-group">
|
||||||
|
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col" i18n>Name</div>
|
||||||
|
<div class="col" i18n>Account</div>
|
||||||
|
<div class="col" i18n>Actions</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li *ngFor="let rule of mailRules" class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailRule(rule)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.MailRule)">{{rule.name}}</button></div>
|
||||||
|
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" [disabled]="!userCanEdit(rule)" class="btn btn-sm btn-primary" type="button" (click)="editMailRule(rule)" i18n>Edit</button>
|
||||||
|
<button *pngxIfOwner="rule" class="btn btn-sm btn-primary" type="button" (click)="editPermissions(rule)" i18n>Permissions</button>
|
||||||
|
<button *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.MailRule }" [disabled]="!userIsOwner(rule)" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)" i18n>Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<div *ngIf="mailRules.length === 0" i18n>No mail rules defined.</div>
|
||||||
|
</ul>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div *ngIf="!mailAccounts || !mailRules">
|
||||||
|
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||||
|
<div class="visually-hidden" i18n>Loading...</div>
|
||||||
|
</div>
|
304
src-ui/src/app/components/manage/mail/mail.component.spec.ts
Normal file
304
src-ui/src/app/components/manage/mail/mail.component.spec.ts
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||||
|
|
||||||
|
import { MailComponent } from './mail.component'
|
||||||
|
import { DatePipe } from '@angular/common'
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing'
|
||||||
|
import {
|
||||||
|
NgbModule,
|
||||||
|
NgbAlertModule,
|
||||||
|
NgbModalRef,
|
||||||
|
NgbModal,
|
||||||
|
} 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'
|
||||||
|
import { PaperlessMailRule } 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 { 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 { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
||||||
|
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
||||||
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
|
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
||||||
|
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
||||||
|
import { CheckComponent } from '../../common/input/check/check.component'
|
||||||
|
import { NumberComponent } from '../../common/input/number/number.component'
|
||||||
|
import { PasswordComponent } from '../../common/input/password/password.component'
|
||||||
|
import { PermissionsFormComponent } from '../../common/input/permissions/permissions-form/permissions-form.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 { TextComponent } from '../../common/input/text/text.component'
|
||||||
|
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||||
|
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
|
||||||
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
|
import { TagsComponent } from '../../common/input/tags/tags.component'
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
|
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||||
|
|
||||||
|
const mailAccounts = [
|
||||||
|
{ id: 1, name: 'account1' },
|
||||||
|
{ id: 2, name: 'account2' },
|
||||||
|
]
|
||||||
|
const mailRules = [
|
||||||
|
{ id: 1, name: 'rule1', owner: 1, account: 1 },
|
||||||
|
{ id: 2, name: 'rule2', owner: 2, account: 2 },
|
||||||
|
]
|
||||||
|
|
||||||
|
describe('MailComponent', () => {
|
||||||
|
let component: MailComponent
|
||||||
|
let fixture: ComponentFixture<MailComponent>
|
||||||
|
let mailAccountService: MailAccountService
|
||||||
|
let mailRuleService: MailRuleService
|
||||||
|
let modalService: NgbModal
|
||||||
|
let toastService: ToastService
|
||||||
|
let permissionsService: PermissionsService
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
MailComponent,
|
||||||
|
PageHeaderComponent,
|
||||||
|
IfPermissionsDirective,
|
||||||
|
CustomDatePipe,
|
||||||
|
ConfirmDialogComponent,
|
||||||
|
CheckComponent,
|
||||||
|
SafeHtmlPipe,
|
||||||
|
SelectComponent,
|
||||||
|
TextComponent,
|
||||||
|
PasswordComponent,
|
||||||
|
NumberComponent,
|
||||||
|
MailAccountEditDialogComponent,
|
||||||
|
MailRuleEditDialogComponent,
|
||||||
|
IfOwnerDirective,
|
||||||
|
TagsComponent,
|
||||||
|
PermissionsUserComponent,
|
||||||
|
PermissionsGroupComponent,
|
||||||
|
PermissionsDialogComponent,
|
||||||
|
PermissionsFormComponent,
|
||||||
|
],
|
||||||
|
providers: [CustomDatePipe, DatePipe, PermissionsGuard],
|
||||||
|
imports: [
|
||||||
|
NgbModule,
|
||||||
|
HttpClientTestingModule,
|
||||||
|
RouterTestingModule.withRoutes(routes),
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
NgbAlertModule,
|
||||||
|
NgSelectModule,
|
||||||
|
],
|
||||||
|
}).compileComponents()
|
||||||
|
|
||||||
|
mailAccountService = TestBed.inject(MailAccountService)
|
||||||
|
mailRuleService = TestBed.inject(MailRuleService)
|
||||||
|
modalService = TestBed.inject(NgbModal)
|
||||||
|
toastService = TestBed.inject(ToastService)
|
||||||
|
permissionsService = TestBed.inject(PermissionsService)
|
||||||
|
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||||
|
jest
|
||||||
|
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
|
||||||
|
.mockReturnValue(true)
|
||||||
|
jest
|
||||||
|
.spyOn(permissionsService, 'currentUserOwnsObject')
|
||||||
|
.mockReturnValue(true)
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MailComponent)
|
||||||
|
component = fixture.componentInstance
|
||||||
|
fixture.detectChanges()
|
||||||
|
})
|
||||||
|
|
||||||
|
function completeSetup(excludeService = null) {
|
||||||
|
if (excludeService !== mailAccountService) {
|
||||||
|
jest.spyOn(mailAccountService, 'listAll').mockReturnValue(
|
||||||
|
of({
|
||||||
|
all: mailAccounts.map((a) => a.id),
|
||||||
|
count: mailAccounts.length,
|
||||||
|
results: (mailAccounts as PaperlessMailAccount[]).concat([]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (excludeService !== mailRuleService) {
|
||||||
|
jest.spyOn(mailRuleService, 'listAll').mockReturnValue(
|
||||||
|
of({
|
||||||
|
all: mailRules.map((r) => r.id),
|
||||||
|
count: mailRules.length,
|
||||||
|
results: (mailRules as PaperlessMailRule[]).concat([]),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MailComponent)
|
||||||
|
component = fixture.componentInstance
|
||||||
|
fixture.detectChanges()
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should show errors on load if load mailAccounts failure', () => {
|
||||||
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
|
jest
|
||||||
|
.spyOn(mailAccountService, 'listAll')
|
||||||
|
.mockImplementation(() =>
|
||||||
|
throwError(() => new Error('failed to load mail accounts'))
|
||||||
|
)
|
||||||
|
completeSetup(mailAccountService)
|
||||||
|
expect(toastErrorSpy).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should show errors on load if load mailRules failure', () => {
|
||||||
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
|
jest
|
||||||
|
.spyOn(mailRuleService, 'listAll')
|
||||||
|
.mockImplementation(() =>
|
||||||
|
throwError(() => new Error('failed to load mail rules'))
|
||||||
|
)
|
||||||
|
completeSetup(mailRuleService)
|
||||||
|
expect(toastErrorSpy).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support edit / create mail account, show error if needed', () => {
|
||||||
|
completeSetup()
|
||||||
|
let modal: NgbModalRef
|
||||||
|
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||||
|
component.editMailAccount(mailAccounts[0] as PaperlessMailAccount)
|
||||||
|
let editDialog = modal.componentInstance as MailAccountEditDialogComponent
|
||||||
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
|
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
|
editDialog.failed.emit()
|
||||||
|
expect(toastErrorSpy).toBeCalled()
|
||||||
|
editDialog.succeeded.emit(mailAccounts[0])
|
||||||
|
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||||
|
`Saved account "${mailAccounts[0].name}".`
|
||||||
|
)
|
||||||
|
editDialog.cancel()
|
||||||
|
component.editMailAccount()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support delete mail account, show error if needed', () => {
|
||||||
|
completeSetup()
|
||||||
|
let modal: NgbModalRef
|
||||||
|
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||||
|
component.deleteMailAccount(mailAccounts[0] as PaperlessMailAccount)
|
||||||
|
const deleteDialog = modal.componentInstance as ConfirmDialogComponent
|
||||||
|
const deleteSpy = jest.spyOn(mailAccountService, 'delete')
|
||||||
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
|
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
|
const listAllSpy = jest.spyOn(mailAccountService, 'listAll')
|
||||||
|
deleteSpy.mockReturnValueOnce(
|
||||||
|
throwError(() => new Error('error deleting mail account'))
|
||||||
|
)
|
||||||
|
deleteDialog.confirm()
|
||||||
|
expect(toastErrorSpy).toBeCalled()
|
||||||
|
deleteSpy.mockReturnValueOnce(of(true))
|
||||||
|
deleteDialog.confirm()
|
||||||
|
expect(listAllSpy).toHaveBeenCalled()
|
||||||
|
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail account')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support edit / create mail rule, show error if needed', () => {
|
||||||
|
completeSetup()
|
||||||
|
let modal: NgbModalRef
|
||||||
|
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||||
|
component.editMailRule(mailRules[0] as PaperlessMailRule)
|
||||||
|
const editDialog = modal.componentInstance as MailRuleEditDialogComponent
|
||||||
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
|
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
|
editDialog.failed.emit()
|
||||||
|
expect(toastErrorSpy).toBeCalled()
|
||||||
|
editDialog.succeeded.emit(mailRules[0])
|
||||||
|
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||||
|
`Saved rule "${mailRules[0].name}".`
|
||||||
|
)
|
||||||
|
editDialog.cancel()
|
||||||
|
component.editMailRule()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support delete mail rule, show error if needed', () => {
|
||||||
|
completeSetup()
|
||||||
|
let modal: NgbModalRef
|
||||||
|
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||||
|
component.deleteMailRule(mailRules[0] as PaperlessMailRule)
|
||||||
|
const deleteDialog = modal.componentInstance as ConfirmDialogComponent
|
||||||
|
const deleteSpy = jest.spyOn(mailRuleService, 'delete')
|
||||||
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
|
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
|
const listAllSpy = jest.spyOn(mailRuleService, 'listAll')
|
||||||
|
deleteSpy.mockReturnValueOnce(
|
||||||
|
throwError(() => new Error('error deleting mail rule'))
|
||||||
|
)
|
||||||
|
deleteDialog.confirm()
|
||||||
|
expect(toastErrorSpy).toBeCalled()
|
||||||
|
deleteSpy.mockReturnValueOnce(of(true))
|
||||||
|
deleteDialog.confirm()
|
||||||
|
expect(listAllSpy).toHaveBeenCalled()
|
||||||
|
expect(toastInfoSpy).toHaveBeenCalledWith('Deleted mail rule')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support edit permissions on mail rule objects', () => {
|
||||||
|
completeSetup()
|
||||||
|
const perms = {
|
||||||
|
owner: 99,
|
||||||
|
set_permissions: {
|
||||||
|
view: {
|
||||||
|
users: [1],
|
||||||
|
groups: [2],
|
||||||
|
},
|
||||||
|
change: {
|
||||||
|
users: [3],
|
||||||
|
groups: [4],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
let modal: NgbModalRef
|
||||||
|
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||||
|
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||||
|
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||||
|
const rulePatchSpy = jest.spyOn(mailRuleService, 'patch')
|
||||||
|
component.editPermissions(mailRules[0] as PaperlessMailRule)
|
||||||
|
expect(modal).not.toBeUndefined()
|
||||||
|
let dialog = modal.componentInstance as PermissionsDialogComponent
|
||||||
|
expect(dialog.object).toEqual(mailRules[0])
|
||||||
|
|
||||||
|
rulePatchSpy.mockReturnValueOnce(
|
||||||
|
throwError(() => new Error('error saving perms'))
|
||||||
|
)
|
||||||
|
dialog.confirmClicked.emit(perms)
|
||||||
|
expect(rulePatchSpy).toHaveBeenCalled()
|
||||||
|
expect(toastErrorSpy).toHaveBeenCalled()
|
||||||
|
rulePatchSpy.mockReturnValueOnce(of(mailRules[0] as PaperlessMailRule))
|
||||||
|
dialog.confirmClicked.emit(perms)
|
||||||
|
expect(toastInfoSpy).toHaveBeenCalledWith('Permissions updated')
|
||||||
|
|
||||||
|
modalService.dismissAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should support edit permissions on mail account objects', () => {
|
||||||
|
completeSetup()
|
||||||
|
const perms = {
|
||||||
|
owner: 99,
|
||||||
|
set_permissions: {
|
||||||
|
view: {
|
||||||
|
users: [1],
|
||||||
|
groups: [2],
|
||||||
|
},
|
||||||
|
change: {
|
||||||
|
users: [3],
|
||||||
|
groups: [4],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
let modal: NgbModalRef
|
||||||
|
modalService.activeInstances.subscribe((refs) => (modal = refs[0]))
|
||||||
|
const accountPatchSpy = jest.spyOn(mailAccountService, 'patch')
|
||||||
|
component.editPermissions(mailAccounts[0] as PaperlessMailAccount)
|
||||||
|
expect(modal).not.toBeUndefined()
|
||||||
|
let dialog = modal.componentInstance as PermissionsDialogComponent
|
||||||
|
expect(dialog.object).toEqual(mailAccounts[0])
|
||||||
|
dialog = modal.componentInstance as PermissionsDialogComponent
|
||||||
|
dialog.confirmClicked.emit(perms)
|
||||||
|
expect(accountPatchSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
232
src-ui/src/app/components/manage/mail/mail.component.ts
Normal file
232
src-ui/src/app/components/manage/mail/mail.component.ts
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import { Component, OnInit, OnDestroy } from '@angular/core'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { Subject, first, takeUntil } from 'rxjs'
|
||||||
|
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
|
||||||
|
import { PaperlessMailAccount } from 'src/app/data/paperless-mail-account'
|
||||||
|
import { PaperlessMailRule } from 'src/app/data/paperless-mail-rule'
|
||||||
|
import {
|
||||||
|
PermissionsService,
|
||||||
|
PermissionAction,
|
||||||
|
} from 'src/app/services/permissions.service'
|
||||||
|
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'
|
||||||
|
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
||||||
|
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
||||||
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
|
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||||
|
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
||||||
|
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
||||||
|
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
|
||||||
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pngx-mail',
|
||||||
|
templateUrl: './mail.component.html',
|
||||||
|
styleUrls: ['./mail.component.scss'],
|
||||||
|
})
|
||||||
|
export class MailComponent
|
||||||
|
extends ComponentWithPermissions
|
||||||
|
implements OnInit, OnDestroy
|
||||||
|
{
|
||||||
|
mailAccounts: PaperlessMailAccount[] = []
|
||||||
|
mailRules: PaperlessMailRule[] = []
|
||||||
|
|
||||||
|
unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public mailAccountService: MailAccountService,
|
||||||
|
public mailRuleService: MailRuleService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
private modalService: NgbModal,
|
||||||
|
public permissionsService: PermissionsService
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.mailAccountService
|
||||||
|
.listAll(null, null, { full_perms: true })
|
||||||
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe({
|
||||||
|
next: (r) => {
|
||||||
|
this.mailAccounts = r.results
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError(
|
||||||
|
$localize`Error retrieving mail accounts`,
|
||||||
|
e
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
this.mailRuleService
|
||||||
|
.listAll(null, null, { full_perms: true })
|
||||||
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe({
|
||||||
|
next: (r) => {
|
||||||
|
this.mailRules = r.results
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError($localize`Error retrieving mail rules`, e)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.unsubscribeNotifier.next(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
editMailAccount(account: PaperlessMailAccount = null) {
|
||||||
|
const modal = this.modalService.open(MailAccountEditDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
size: 'xl',
|
||||||
|
})
|
||||||
|
modal.componentInstance.dialogMode = account
|
||||||
|
? EditDialogMode.EDIT
|
||||||
|
: EditDialogMode.CREATE
|
||||||
|
modal.componentInstance.object = account
|
||||||
|
modal.componentInstance.succeeded
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((newMailAccount) => {
|
||||||
|
this.toastService.showInfo(
|
||||||
|
$localize`Saved account "${newMailAccount.name}".`
|
||||||
|
)
|
||||||
|
this.mailAccountService.clearCache()
|
||||||
|
this.mailAccountService
|
||||||
|
.listAll(null, null, { full_perms: true })
|
||||||
|
.subscribe((r) => {
|
||||||
|
this.mailAccounts = r.results
|
||||||
|
})
|
||||||
|
})
|
||||||
|
modal.componentInstance.failed
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((e) => {
|
||||||
|
this.toastService.showError($localize`Error saving account.`, e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMailAccount(account: PaperlessMailAccount) {
|
||||||
|
const modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.title = $localize`Confirm delete mail account`
|
||||||
|
modal.componentInstance.messageBold = $localize`This operation will permanently delete this mail 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.mailAccountService.delete(account).subscribe({
|
||||||
|
next: () => {
|
||||||
|
modal.close()
|
||||||
|
this.toastService.showInfo($localize`Deleted mail account`)
|
||||||
|
this.mailAccountService.clearCache()
|
||||||
|
this.mailAccountService
|
||||||
|
.listAll(null, null, { full_perms: true })
|
||||||
|
.subscribe((r) => {
|
||||||
|
this.mailAccounts = r.results
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError(
|
||||||
|
$localize`Error deleting mail account.`,
|
||||||
|
e
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editMailRule(rule: PaperlessMailRule = null) {
|
||||||
|
const modal = this.modalService.open(MailRuleEditDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
size: 'xl',
|
||||||
|
})
|
||||||
|
modal.componentInstance.dialogMode = rule
|
||||||
|
? EditDialogMode.EDIT
|
||||||
|
: EditDialogMode.CREATE
|
||||||
|
modal.componentInstance.object = rule
|
||||||
|
modal.componentInstance.succeeded
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((newMailRule) => {
|
||||||
|
this.toastService.showInfo($localize`Saved rule "${newMailRule.name}".`)
|
||||||
|
this.mailRuleService.clearCache()
|
||||||
|
this.mailRuleService
|
||||||
|
.listAll(null, null, { full_perms: true })
|
||||||
|
.subscribe((r) => {
|
||||||
|
this.mailRules = r.results
|
||||||
|
})
|
||||||
|
})
|
||||||
|
modal.componentInstance.failed
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((e) => {
|
||||||
|
this.toastService.showError($localize`Error saving rule.`, e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMailRule(rule: PaperlessMailRule) {
|
||||||
|
const modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.title = $localize`Confirm delete mail rule`
|
||||||
|
modal.componentInstance.messageBold = $localize`This operation will permanently delete this mail rule.`
|
||||||
|
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.mailRuleService.delete(rule).subscribe({
|
||||||
|
next: () => {
|
||||||
|
modal.close()
|
||||||
|
this.toastService.showInfo($localize`Deleted mail rule`)
|
||||||
|
this.mailRuleService.clearCache()
|
||||||
|
this.mailRuleService
|
||||||
|
.listAll(null, null, { full_perms: true })
|
||||||
|
.subscribe((r) => {
|
||||||
|
this.mailRules = r.results
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError($localize`Error deleting mail rule.`, e)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editPermissions(object: PaperlessMailRule | PaperlessMailAccount) {
|
||||||
|
const modal = this.modalService.open(PermissionsDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
const dialog: PermissionsDialogComponent =
|
||||||
|
modal.componentInstance as PermissionsDialogComponent
|
||||||
|
dialog.object = object
|
||||||
|
modal.componentInstance.confirmClicked.subscribe((permissions) => {
|
||||||
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
const service: AbstractPaperlessService<
|
||||||
|
PaperlessMailRule | PaperlessMailAccount
|
||||||
|
> = 'account' in object ? this.mailRuleService : this.mailAccountService
|
||||||
|
object.owner = permissions['owner']
|
||||||
|
object['set_permissions'] = permissions['set_permissions']
|
||||||
|
service.patch(object).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.toastService.showInfo($localize`Permissions updated`)
|
||||||
|
modal.close()
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError($localize`Error updating permissions`, e)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
userCanEdit(obj: ObjectWithPermissions): boolean {
|
||||||
|
return this.permissionsService.currentUserHasObjectPermissions(
|
||||||
|
PermissionAction.Change,
|
||||||
|
obj
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
userIsOwner(obj: ObjectWithPermissions): boolean {
|
||||||
|
return this.permissionsService.currentUserOwnsObject(obj)
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,8 @@ export class MailAccountService extends AbstractPaperlessService<PaperlessMailAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
test(o: PaperlessMailAccount) {
|
test(o: PaperlessMailAccount) {
|
||||||
return this.http.post(this.getResourceUrl() + 'test/', o)
|
const account = Object.assign({}, o)
|
||||||
|
delete account['set_permissions']
|
||||||
|
return this.http.post(this.getResourceUrl() + 'test/', account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user