diff --git a/src-ui/e2e/settings/requests/api-settings.har b/src-ui/e2e/admin/requests/api-settings.har similarity index 100% rename from src-ui/e2e/settings/requests/api-settings.har rename to src-ui/e2e/admin/requests/api-settings.har diff --git a/src-ui/e2e/settings/settings.spec.ts b/src-ui/e2e/admin/settings.spec.ts similarity index 59% rename from src-ui/e2e/settings/settings.spec.ts rename to src-ui/e2e/admin/settings.spec.ts index 1ae9afa06..92c6918d9 100644 --- a/src-ui/e2e/settings/settings.spec.ts +++ b/src-ui/e2e/admin/settings.spec.ts @@ -1,24 +1,6 @@ import { test, expect } from '@playwright/test' -const REQUESTS_HAR = 'e2e/settings/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 -}) +const REQUESTS_HAR = 'e2e/admin/requests/api-settings.har' test('should activate / deactivate save button when settings change', async ({ page, @@ -72,30 +54,3 @@ test('should toggle saved view options when set & saved', async ({ page }) => { await page.getByRole('button', { name: 'Save' }).click() await updatePromise }) - -test('should support tab direct navigation', async ({ page }) => { - await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' }) - await page.goto('/settings/general') - await expect(page.getByRole('tab', { name: 'General' })).toHaveAttribute( - 'aria-selected', - 'true' - ) - await page.goto('/settings/notifications') - await expect( - page.getByRole('tab', { name: 'Notifications' }) - ).toHaveAttribute('aria-selected', 'true') - await page.goto('/settings/savedviews') - await expect(page.getByRole('tab', { name: 'Saved Views' })).toHaveAttribute( - 'aria-selected', - 'true' - ) - await page.goto('/settings/mail') - await expect(page.getByRole('tab', { name: 'Mail' })).toHaveAttribute( - 'aria-selected', - 'true' - ) - await page.goto('/settings/usersgroups') - await expect( - page.getByRole('tab', { name: 'Users & Groups' }) - ).toHaveAttribute('aria-selected', 'true') -}) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 98bd9af7d..d1a98668a 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -371,46 +371,1580 @@ 176 - - File Tasks shows you documents that have been consumed, are waiting to be, or may have failed during the process. + + Manage e-mail accounts and rules for automatically importing documents. src/app/app.component.ts 184 - - Check out the settings for various tweaks to the web app, toggle settings for saved views or setup e-mail checking. + + Consumption templates give you finer control over the document ingestion process. src/app/app.component.ts 192 + + File Tasks shows you documents that have been consumed, are waiting to be, or may have failed during the process. + + src/app/app.component.ts + 200 + + + + Check out the settings for various tweaks to the web app and toggle settings for saved views. + + src/app/app.component.ts + 208 + + Thank you! 🙏 src/app/app.component.ts - 200 + 216 There are <em>tons</em> more features and info we didn't cover here, but this should get you started. Check out the documentation or visit the project on GitHub to learn more or to report issues. src/app/app.component.ts - 202 + 218 Lastly, on behalf of every contributor to this community-supported project, thank you for using Paperless-ngx! src/app/app.component.ts - 204 + 220 Initiating upload... src/app/app.component.ts - 273 + 289 + + + + Logs + + src/app/components/admin/logs/logs.component.html + 1 + + + src/app/components/app-frame/app-frame.component.html + 201 + + + src/app/components/app-frame/app-frame.component.html + 204 + + + + Loading... + + src/app/components/admin/logs/logs.component.html + 11 + + + src/app/components/admin/logs/logs.component.html + 20 + + + src/app/components/admin/settings/settings.component.html + 312 + + + src/app/components/admin/tasks/tasks.component.html + 19 + + + src/app/components/admin/tasks/tasks.component.html + 27 + + + src/app/components/admin/users-groups/users-groups.component.html + 82 + + + src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html + 30 + + + src/app/components/common/permissions-dialog/permissions-dialog.component.html + 18 + + + src/app/components/dashboard/dashboard.component.html + 10 + + + src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html + 7 + + + src/app/components/document-list/document-list.component.html + 95 + + + src/app/components/manage/mail/mail.component.html + 82 + + + src/app/components/manage/management-list/management-list.component.html + 46 + + + src/app/components/manage/management-list/management-list.component.html + 46 + + + src/app/components/manage/management-list/management-list.component.html + 46 + + + src/app/components/manage/management-list/management-list.component.html + 46 + + + + Settings + + src/app/components/admin/settings/settings.component.html + 1 + + + src/app/components/admin/settings/settings.component.html + 274 + + + src/app/components/app-frame/app-frame.component.html + 45 + + + src/app/components/app-frame/app-frame.component.html + 179 + + + src/app/components/app-frame/app-frame.component.html + 182 + + + + Start tour + + src/app/components/admin/settings/settings.component.html + 2 + + + + Open Django Admin + + src/app/components/admin/settings/settings.component.html + 4 + + + + General + + src/app/components/admin/settings/settings.component.html + 15 + + + + Appearance + + src/app/components/admin/settings/settings.component.html + 18 + + + + Display language + + src/app/components/admin/settings/settings.component.html + 22 + + + + You need to reload the page after applying a new language. + + src/app/components/admin/settings/settings.component.html + 30 + + + + Date display + + src/app/components/admin/settings/settings.component.html + 37 + + + + Date format + + src/app/components/admin/settings/settings.component.html + 50 + + + + Short: + + src/app/components/admin/settings/settings.component.html + 56,57 + + + + Medium: + + src/app/components/admin/settings/settings.component.html + 60,61 + + + + Long: + + src/app/components/admin/settings/settings.component.html + 64,65 + + + + Items per page + + src/app/components/admin/settings/settings.component.html + 72 + + + + Document editor + + src/app/components/admin/settings/settings.component.html + 88 + + + + Use PDF viewer provided by the browser + + src/app/components/admin/settings/settings.component.html + 92 + + + + This is usually faster for displaying large PDF documents, but it might not work on some browsers. + + src/app/components/admin/settings/settings.component.html + 92 + + + + Sidebar + + src/app/components/admin/settings/settings.component.html + 99 + + + + Use 'slim' sidebar (icons only) + + src/app/components/admin/settings/settings.component.html + 103 + + + + Dark mode + + src/app/components/admin/settings/settings.component.html + 110 + + + + Use system settings + + src/app/components/admin/settings/settings.component.html + 113 + + + + Enable dark mode + + src/app/components/admin/settings/settings.component.html + 114 + + + + Invert thumbnails in dark mode + + src/app/components/admin/settings/settings.component.html + 115 + + + + Theme Color + + src/app/components/admin/settings/settings.component.html + 121 + + + + Reset + + src/app/components/admin/settings/settings.component.html + 130 + + + + Update checking + + src/app/components/admin/settings/settings.component.html + 135 + + + + Update checking works by pinging the public GitHub API for the latest release to determine whether a new version is available. Actual updating of the app must still be performed manually. + + src/app/components/admin/settings/settings.component.html + 139,142 + + + + No tracking data is collected by the app in any way. + + src/app/components/admin/settings/settings.component.html + 144,146 + + + + Enable update checking + + src/app/components/admin/settings/settings.component.html + 146 + + + + Bulk editing + + src/app/components/admin/settings/settings.component.html + 150 + + + + Show confirmation dialogs + + src/app/components/admin/settings/settings.component.html + 154 + + + + Deleting documents will always ask for confirmation. + + src/app/components/admin/settings/settings.component.html + 154 + + + + Apply on close + + src/app/components/admin/settings/settings.component.html + 155 + + + + Notes + + src/app/components/admin/settings/settings.component.html + 159 + + + src/app/components/document-list/document-list.component.html + 163 + + + src/app/services/rest/document.service.ts + 25 + + + + Enable notes + + src/app/components/admin/settings/settings.component.html + 163 + + + + Permissions + + src/app/components/admin/settings/settings.component.html + 171 + + + src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html + 11 + + + src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html + 30 + + + src/app/components/common/input/permissions/permissions-form/permissions-form.component.html + 2 + + + src/app/components/document-detail/document-detail.component.html + 192 + + + src/app/components/document-list/bulk-editor/bulk-editor.component.html + 78 + + + src/app/components/document-list/filter-editor/filter-editor.component.html + 76 + + + src/app/components/manage/mail/mail.component.html + 31 + + + src/app/components/manage/mail/mail.component.html + 69 + + + src/app/components/manage/management-list/management-list.component.html + 10 + + + src/app/components/manage/management-list/management-list.component.html + 10 + + + src/app/components/manage/management-list/management-list.component.html + 10 + + + src/app/components/manage/management-list/management-list.component.html + 10 + + + + Default Permissions + + src/app/components/admin/settings/settings.component.html + 174 + + + + Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI + + src/app/components/admin/settings/settings.component.html + 178,180 + + + + Default Owner + + src/app/components/admin/settings/settings.component.html + 185 + + + + Objects without an owner can be viewed and edited by all users + + src/app/components/admin/settings/settings.component.html + 189 + + + src/app/components/common/input/permissions/permissions-form/permissions-form.component.html + 25 + + + + Default View Permissions + + src/app/components/admin/settings/settings.component.html + 194 + + + + Users: + + src/app/components/admin/settings/settings.component.html + 199 + + + src/app/components/admin/settings/settings.component.html + 226 + + + src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html + 46 + + + src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html + 65 + + + src/app/components/common/input/permissions/permissions-form/permissions-form.component.html + 31 + + + src/app/components/common/input/permissions/permissions-form/permissions-form.component.html + 50 + + + + Groups: + + src/app/components/admin/settings/settings.component.html + 209 + + + src/app/components/admin/settings/settings.component.html + 236 + + + src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html + 54 + + + src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html + 73 + + + src/app/components/common/input/permissions/permissions-form/permissions-form.component.html + 39 + + + src/app/components/common/input/permissions/permissions-form/permissions-form.component.html + 58 + + + + Default Edit Permissions + + src/app/components/admin/settings/settings.component.html + 221 + + + + Edit permissions also grant viewing permissions + + src/app/components/admin/settings/settings.component.html + 245 + + + src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html + 79 + + + src/app/components/common/input/permissions/permissions-form/permissions-form.component.html + 64 + + + + Notifications + + src/app/components/admin/settings/settings.component.html + 253 + + + + Document processing + + src/app/components/admin/settings/settings.component.html + 256 + + + + Show notifications when new documents are detected + + src/app/components/admin/settings/settings.component.html + 260 + + + + Show notifications when document processing completes successfully + + src/app/components/admin/settings/settings.component.html + 261 + + + + Show notifications when document processing fails + + src/app/components/admin/settings/settings.component.html + 262 + + + + Suppress notifications on dashboard + + src/app/components/admin/settings/settings.component.html + 263 + + + + This will suppress all messages about document processing status on the dashboard. + + src/app/components/admin/settings/settings.component.html + 263 + + + + Saved views + + src/app/components/admin/settings/settings.component.html + 271 + + + src/app/components/app-frame/app-frame.component.html + 85 + + + + Show warning when closing saved views with unsaved changes + + src/app/components/admin/settings/settings.component.html + 277 + + + + Views + + src/app/components/admin/settings/settings.component.html + 281 + + + src/app/components/document-list/document-list.component.html + 64 + + + + Name + + src/app/components/admin/settings/settings.component.html + 286 + + + src/app/components/admin/tasks/tasks.component.html + 40 + + + src/app/components/admin/users-groups/users-groups.component.html + 19 + + + src/app/components/admin/users-groups/users-groups.component.html + 55 + + + src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html + 10 + + + src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html + 9 + + + src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html + 10 + + + src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html + 10 + + + src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html + 10 + + + src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html + 10 + + + src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html + 9 + + + src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html + 8 + + + src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html + 8 + + + src/app/components/manage/consumption-templates/consumption-templates.component.html + 14 + + + src/app/components/manage/mail/mail.component.html + 18 + + + src/app/components/manage/mail/mail.component.html + 56 + + + src/app/components/manage/management-list/management-list.component.html + 19 + + + src/app/components/manage/management-list/management-list.component.html + 19 + + + src/app/components/manage/management-list/management-list.component.html + 19 + + + src/app/components/manage/management-list/management-list.component.html + 19 + + + src/app/components/manage/management-list/management-list.component.html + 35 + + + src/app/components/manage/management-list/management-list.component.html + 35 + + + src/app/components/manage/management-list/management-list.component.html + 35 + + + src/app/components/manage/management-list/management-list.component.html + 35 + + + +  Appears on + + src/app/components/admin/settings/settings.component.html + 291,292 + + + + Show on dashboard + + src/app/components/admin/settings/settings.component.html + 294 + + + src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html + 10 + + + + Show in sidebar + + src/app/components/admin/settings/settings.component.html + 298 + + + src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html + 9 + + + + Actions + + src/app/components/admin/settings/settings.component.html + 303 + + + src/app/components/admin/tasks/tasks.component.html + 44 + + + src/app/components/admin/users-groups/users-groups.component.html + 21 + + + src/app/components/admin/users-groups/users-groups.component.html + 58 + + + src/app/components/document-detail/document-detail.component.html + 34 + + + src/app/components/document-list/bulk-editor/bulk-editor.component.html + 86 + + + src/app/components/manage/consumption-templates/consumption-templates.component.html + 17 + + + src/app/components/manage/mail/mail.component.html + 20 + + + src/app/components/manage/mail/mail.component.html + 58 + + + src/app/components/manage/management-list/management-list.component.html + 39 + + + src/app/components/manage/management-list/management-list.component.html + 39 + + + src/app/components/manage/management-list/management-list.component.html + 39 + + + src/app/components/manage/management-list/management-list.component.html + 39 + + + + Delete + + src/app/components/admin/settings/settings.component.html + 304 + + + src/app/components/admin/users-groups/users-groups.component.html + 33 + + + src/app/components/admin/users-groups/users-groups.component.html + 70 + + + src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts + 53 + + + src/app/components/common/permissions-select/permissions-select.component.html + 9 + + + src/app/components/common/share-links-dropdown/share-links-dropdown.component.html + 33 + + + src/app/components/document-detail/document-detail.component.html + 11 + + + src/app/components/document-list/bulk-editor/bulk-editor.component.html + 138 + + + src/app/components/manage/consumption-templates/consumption-templates.component.html + 29 + + + src/app/components/manage/mail/mail.component.html + 32 + + + src/app/components/manage/mail/mail.component.html + 70 + + + src/app/components/manage/management-list/management-list.component.html + 74 + + + src/app/components/manage/management-list/management-list.component.html + 74 + + + src/app/components/manage/management-list/management-list.component.html + 74 + + + src/app/components/manage/management-list/management-list.component.html + 74 + + + src/app/components/manage/management-list/management-list.component.html + 92 + + + src/app/components/manage/management-list/management-list.component.html + 92 + + + src/app/components/manage/management-list/management-list.component.html + 92 + + + src/app/components/manage/management-list/management-list.component.html + 92 + + + src/app/components/manage/management-list/management-list.component.ts + 205 + + + + No saved views defined. + + src/app/components/admin/settings/settings.component.html + 308 + + + + Save + + src/app/components/admin/settings/settings.component.html + 323 + + + src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html + 90 + + + src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html + 21 + + + src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html + 23 + + + src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html + 17 + + + src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html + 35 + + + src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html + 42 + + + src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html + 22 + + + src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html + 24 + + + src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html + 36 + + + src/app/components/document-detail/document-detail.component.html + 208 + + + src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html + 19 + + + + Error retrieving users + + src/app/components/admin/settings/settings.component.ts + 153 + + + src/app/components/admin/users-groups/users-groups.component.ts + 55 + + + + Error retrieving groups + + src/app/components/admin/settings/settings.component.ts + 172 + + + src/app/components/admin/users-groups/users-groups.component.ts + 67 + + + + Saved view "" deleted. + + src/app/components/admin/settings/settings.component.ts + 363 + + + + Settings saved + + src/app/components/admin/settings/settings.component.ts + 485 + + + + Settings were saved successfully. + + src/app/components/admin/settings/settings.component.ts + 486 + + + + Settings were saved successfully. Reload is required to apply some changes. + + src/app/components/admin/settings/settings.component.ts + 490 + + + + Reload now + + src/app/components/admin/settings/settings.component.ts + 491 + + + + An error occurred while saving settings. + + src/app/components/admin/settings/settings.component.ts + 501 + + + src/app/components/app-frame/app-frame.component.ts + 104 + + + + Use system language + + src/app/components/admin/settings/settings.component.ts + 509 + + + + Use date format of display language + + src/app/components/admin/settings/settings.component.ts + 516 + + + + Error while storing settings on server. + + src/app/components/admin/settings/settings.component.ts + 539 + + + + File Tasks + + src/app/components/admin/tasks/tasks.component.html + 1 + + + src/app/components/app-frame/app-frame.component.html + 193 + + + + Clear selection + + src/app/components/admin/tasks/tasks.component.html + 6 + + + src/app/components/manage/management-list/management-list.component.html + 5 + + + src/app/components/manage/management-list/management-list.component.html + 5 + + + src/app/components/manage/management-list/management-list.component.html + 5 + + + src/app/components/manage/management-list/management-list.component.html + 5 + + + + + + src/app/components/admin/tasks/tasks.component.html + 11 + + + src/app/components/common/input/tags/tags.component.html + 2 + + + src/app/components/common/permissions-select/permissions-select.component.html + 22 + + + + Refresh + + src/app/components/admin/tasks/tasks.component.html + 20 + + + + Created + + src/app/components/admin/tasks/tasks.component.html + 41 + + + src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html + 9 + + + src/app/components/document-list/document-list.component.html + 184 + + + src/app/components/document-list/filter-editor/filter-editor.component.html + 62 + + + src/app/services/rest/document.service.ts + 22 + + + + Results + + src/app/components/admin/tasks/tasks.component.html + 42 + + + + Info + + src/app/components/admin/tasks/tasks.component.html + 43 + + + src/app/components/app-frame/app-frame.component.html + 210 + + + + click for full output + + src/app/components/admin/tasks/tasks.component.html + 66 + + + + Dismiss + + src/app/components/admin/tasks/tasks.component.html + 81 + + + src/app/components/admin/tasks/tasks.component.ts + 64 + + + + Open Document + + src/app/components/admin/tasks/tasks.component.html + 87 + + + + {VAR_PLURAL, plural, =1 {One task} other { total tasks}} + + src/app/components/admin/tasks/tasks.component.html + 103 + + + + Failed + + src/app/components/admin/tasks/tasks.component.html + 110 + + + + Complete + + src/app/components/admin/tasks/tasks.component.html + 116 + + + + Started + + src/app/components/admin/tasks/tasks.component.html + 122 + + + + Queued + + src/app/components/admin/tasks/tasks.component.html + 128 + + + + Dismiss selected + + src/app/components/admin/tasks/tasks.component.ts + 28 + + + + Dismiss all + + src/app/components/admin/tasks/tasks.component.ts + 29 + + + src/app/components/admin/tasks/tasks.component.ts + 62 + + + + Confirm Dismiss All + + src/app/components/admin/tasks/tasks.component.ts + 60 + + + + tasks? + + src/app/components/admin/tasks/tasks.component.ts + 62 + + + + queued + + src/app/components/admin/tasks/tasks.component.ts + 130 + + + + started + + src/app/components/admin/tasks/tasks.component.ts + 132 + + + + completed + + src/app/components/admin/tasks/tasks.component.ts + 134 + + + + failed + + src/app/components/admin/tasks/tasks.component.ts + 136 + + + + Users & Groups + + src/app/components/admin/users-groups/users-groups.component.html + 1 + + + src/app/components/app-frame/app-frame.component.html + 186 + + + src/app/components/app-frame/app-frame.component.html + 189 + + + + Users + + src/app/components/admin/users-groups/users-groups.component.html + 6 + + + src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html + 68 + + + + Add User + + src/app/components/admin/users-groups/users-groups.component.html + 11 + + + + Username + + src/app/components/admin/users-groups/users-groups.component.html + 18 + + + src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html + 16 + + + src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html + 10 + + + + Groups + + src/app/components/admin/users-groups/users-groups.component.html + 20 + + + src/app/components/admin/users-groups/users-groups.component.html + 43 + + + src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html + 27 + + + + Edit + + src/app/components/admin/users-groups/users-groups.component.html + 32 + + + src/app/components/admin/users-groups/users-groups.component.html + 69 + + + src/app/components/common/input/permissions/permissions-form/permissions-form.component.html + 46 + + + src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html + 17 + + + src/app/components/document-list/document-card-large/document-card-large.component.html + 49 + + + src/app/components/document-list/document-card-small/document-card-small.component.html + 83 + + + src/app/components/manage/consumption-templates/consumption-templates.component.html + 28 + + + src/app/components/manage/mail/mail.component.html + 30 + + + src/app/components/manage/mail/mail.component.html + 68 + + + src/app/components/manage/management-list/management-list.component.html + 73 + + + src/app/components/manage/management-list/management-list.component.html + 73 + + + src/app/components/manage/management-list/management-list.component.html + 73 + + + src/app/components/manage/management-list/management-list.component.html + 73 + + + src/app/components/manage/management-list/management-list.component.html + 87 + + + src/app/components/manage/management-list/management-list.component.html + 87 + + + src/app/components/manage/management-list/management-list.component.html + 87 + + + src/app/components/manage/management-list/management-list.component.html + 87 + + + + Add Group + + src/app/components/admin/users-groups/users-groups.component.html + 48 + + + + Password has been changed, you will be logged out momentarily. + + src/app/components/admin/users-groups/users-groups.component.ts + 93 + + + + Saved user "". + + src/app/components/admin/users-groups/users-groups.component.ts + 100 + + + + Error saving user. + + src/app/components/admin/users-groups/users-groups.component.ts + 110 + + + + Confirm delete user account + + src/app/components/admin/users-groups/users-groups.component.ts + 118 + + + + This operation will permanently delete this user account. + + src/app/components/admin/users-groups/users-groups.component.ts + 119 + + + + This operation cannot be undone. + + src/app/components/admin/users-groups/users-groups.component.ts + 120 + + + src/app/components/admin/users-groups/users-groups.component.ts + 170 + + + src/app/components/document-detail/document-detail.component.ts + 656 + + + src/app/components/document-list/bulk-editor/bulk-editor.component.ts + 461 + + + src/app/components/document-list/bulk-editor/bulk-editor.component.ts + 500 + + + src/app/components/manage/consumption-templates/consumption-templates.component.ts + 91 + + + src/app/components/manage/mail/mail.component.ts + 114 + + + src/app/components/manage/mail/mail.component.ts + 173 + + + + Proceed + + src/app/components/admin/users-groups/users-groups.component.ts + 122 + + + src/app/components/admin/users-groups/users-groups.component.ts + 172 + + + src/app/components/document-detail/document-detail.component.ts + 658 + + + src/app/components/document-list/bulk-editor/bulk-editor.component.ts + 502 + + + src/app/components/manage/consumption-templates/consumption-templates.component.ts + 93 + + + src/app/components/manage/mail/mail.component.ts + 116 + + + src/app/components/manage/mail/mail.component.ts + 175 + + + + Deleted user + + src/app/components/admin/users-groups/users-groups.component.ts + 128 + + + + Error deleting user. + + src/app/components/admin/users-groups/users-groups.component.ts + 134 + + + + Saved group "". + + src/app/components/admin/users-groups/users-groups.component.ts + 152 + + + + Error saving group. + + src/app/components/admin/users-groups/users-groups.component.ts + 160 + + + + Confirm delete user group + + src/app/components/admin/users-groups/users-groups.component.ts + 168 + + + + This operation will permanently delete this user group. + + src/app/components/admin/users-groups/users-groups.component.ts + 169 + + + + Deleted group + + src/app/components/admin/users-groups/users-groups.component.ts + 178 + + + + Error deleting group. + + src/app/components/admin/users-groups/users-groups.component.ts + 184 @@ -435,29 +1969,6 @@ 39 - - Settings - - src/app/components/app-frame/app-frame.component.html - 45 - - - src/app/components/app-frame/app-frame.component.html - 181 - - - src/app/components/app-frame/app-frame.component.html - 184 - - - src/app/components/manage/settings/settings.component.html - 1 - - - src/app/components/manage/settings/settings.component.html - 268 - - Logout @@ -511,17 +2022,6 @@ 82 - - Saved views - - src/app/components/app-frame/app-frame.component.html - 85 - - - src/app/components/manage/settings/settings.component.html - 265 - - Open documents @@ -617,127 +2117,97 @@ 162 - - File Tasks + + Mail src/app/components/app-frame/app-frame.component.html 166 - src/app/components/manage/tasks/tasks.component.html - 1 + src/app/components/app-frame/app-frame.component.html + 169 + + + + Administration + + src/app/components/app-frame/app-frame.component.html + 175 File Tasks src/app/components/app-frame/app-frame.component.html - 170 - - - - Logs - - src/app/components/app-frame/app-frame.component.html - 174 - - - src/app/components/app-frame/app-frame.component.html - 177 - - - src/app/components/manage/logs/logs.component.html - 1 - - - - Info - - src/app/components/app-frame/app-frame.component.html - 190 - - - src/app/components/manage/tasks/tasks.component.html - 43 + 197 Documentation src/app/components/app-frame/app-frame.component.html - 194 + 214 src/app/components/app-frame/app-frame.component.html - 197 + 217 GitHub src/app/components/app-frame/app-frame.component.html - 202 + 222 src/app/components/app-frame/app-frame.component.html - 205 + 225 Suggest an idea src/app/components/app-frame/app-frame.component.html - 207 + 227 src/app/components/app-frame/app-frame.component.html - 211 + 231 is available. src/app/components/app-frame/app-frame.component.html - 220 + 240 Click to view. src/app/components/app-frame/app-frame.component.html - 220 + 240 Paperless-ngx can automatically check for updates src/app/components/app-frame/app-frame.component.html - 224 + 244 How does this work? src/app/components/app-frame/app-frame.component.html - 231,233 + 251,253 Update available src/app/components/app-frame/app-frame.component.html - 242 - - - - An error occurred while saving settings. - - src/app/components/app-frame/app-frame.component.ts - 104 - - - src/app/components/manage/settings/settings.component.ts - 696 + 262 @@ -852,105 +2322,6 @@ 57 - - Name - - src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html - 10 - - - src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html - 9 - - - src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html - 10 - - - src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html - 10 - - - src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html - 10 - - - src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 10 - - - src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html - 9 - - - src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html - 8 - - - src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html - 8 - - - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.html - 13 - - - src/app/components/manage/management-list/management-list.component.html - 19 - - - src/app/components/manage/management-list/management-list.component.html - 19 - - - src/app/components/manage/management-list/management-list.component.html - 19 - - - src/app/components/manage/management-list/management-list.component.html - 19 - - - src/app/components/manage/management-list/management-list.component.html - 35 - - - src/app/components/manage/management-list/management-list.component.html - 35 - - - src/app/components/manage/management-list/management-list.component.html - 35 - - - src/app/components/manage/management-list/management-list.component.html - 35 - - - src/app/components/manage/settings/settings.component.html - 280 - - - src/app/components/manage/settings/settings.component.html - 333 - - - src/app/components/manage/settings/settings.component.html - 371 - - - src/app/components/manage/settings/settings.component.html - 423 - - - src/app/components/manage/settings/settings.component.html - 457 - - - src/app/components/manage/tasks/tasks.component.html - 40 - - Sort order @@ -958,8 +2329,8 @@ 13 - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.html - 14 + src/app/components/manage/consumption-templates/consumption-templates.component.html + 15 @@ -1096,60 +2467,6 @@ 42 - - Users: - - src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html - 46 - - - src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html - 65 - - - src/app/components/common/input/permissions/permissions-form/permissions-form.component.html - 31 - - - src/app/components/common/input/permissions/permissions-form/permissions-form.component.html - 50 - - - src/app/components/manage/settings/settings.component.html - 160 - - - src/app/components/manage/settings/settings.component.html - 187 - - - - Groups: - - src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html - 54 - - - src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html - 73 - - - src/app/components/common/input/permissions/permissions-form/permissions-form.component.html - 39 - - - src/app/components/common/input/permissions/permissions-form/permissions-form.component.html - 58 - - - src/app/components/manage/settings/settings.component.html - 170 - - - src/app/components/manage/settings/settings.component.html - 197 - - Assign edit permissions @@ -1157,21 +2474,6 @@ 61 - - Edit permissions also grant viewing permissions - - src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html - 79 - - - src/app/components/common/input/permissions/permissions-form/permissions-form.component.html - 64 - - - src/app/components/manage/settings/settings.component.html - 206 - - Error @@ -1246,57 +2548,6 @@ 18 - - Save - - src/app/components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component.html - 90 - - - src/app/components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component.html - 21 - - - src/app/components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component.html - 23 - - - src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html - 17 - - - src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html - 35 - - - src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html - 42 - - - src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html - 22 - - - src/app/components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component.html - 24 - - - src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html - 36 - - - src/app/components/document-detail/document-detail.component.html - 208 - - - src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html - 19 - - - src/app/components/manage/settings/settings.component.html - 493 - - Consume Folder @@ -1431,61 +2682,6 @@ 113 - - Permissions - - src/app/components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component.html - 11 - - - src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html - 30 - - - src/app/components/common/input/permissions/permissions-form/permissions-form.component.html - 2 - - - src/app/components/document-detail/document-detail.component.html - 192 - - - src/app/components/document-list/bulk-editor/bulk-editor.component.html - 78 - - - src/app/components/document-list/filter-editor/filter-editor.component.html - 76 - - - src/app/components/manage/management-list/management-list.component.html - 10 - - - src/app/components/manage/management-list/management-list.component.html - 10 - - - src/app/components/manage/management-list/management-list.component.html - 10 - - - src/app/components/manage/management-list/management-list.component.html - 10 - - - src/app/components/manage/settings/settings.component.html - 135 - - - src/app/components/manage/settings/settings.component.html - 346 - - - src/app/components/manage/settings/settings.component.html - 384 - - Create new user group @@ -1521,21 +2717,6 @@ 13 - - Username - - src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html - 16 - - - src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html - 10 - - - src/app/components/manage/settings/settings.component.html - 422 - - Password @@ -1568,73 +2749,6 @@ 19 - - Loading... - - src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.html - 30 - - - src/app/components/common/permissions-dialog/permissions-dialog.component.html - 18 - - - src/app/components/dashboard/dashboard.component.html - 10 - - - src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html - 7 - - - src/app/components/document-list/document-list.component.html - 95 - - - src/app/components/manage/logs/logs.component.html - 11 - - - src/app/components/manage/logs/logs.component.html - 20 - - - src/app/components/manage/management-list/management-list.component.html - 46 - - - src/app/components/manage/management-list/management-list.component.html - 46 - - - src/app/components/manage/management-list/management-list.component.html - 46 - - - src/app/components/manage/management-list/management-list.component.html - 46 - - - src/app/components/manage/settings/settings.component.html - 306 - - - src/app/components/manage/settings/settings.component.html - 398 - - - src/app/components/manage/settings/settings.component.html - 484 - - - src/app/components/manage/tasks/tasks.component.html - 19 - - - src/app/components/manage/tasks/tasks.component.html - 27 - - Test @@ -1698,8 +2812,8 @@ 11 - src/app/components/manage/settings/settings.component.html - 372 + src/app/components/manage/mail/mail.component.html + 57 @@ -1881,89 +2995,6 @@ 46 - - Delete - - src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts - 53 - - - src/app/components/common/permissions-select/permissions-select.component.html - 9 - - - src/app/components/common/share-links-dropdown/share-links-dropdown.component.html - 33 - - - src/app/components/document-detail/document-detail.component.html - 11 - - - src/app/components/document-list/bulk-editor/bulk-editor.component.html - 138 - - - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.html - 27 - - - src/app/components/manage/management-list/management-list.component.html - 74 - - - src/app/components/manage/management-list/management-list.component.html - 74 - - - src/app/components/manage/management-list/management-list.component.html - 74 - - - src/app/components/manage/management-list/management-list.component.html - 74 - - - src/app/components/manage/management-list/management-list.component.html - 92 - - - src/app/components/manage/management-list/management-list.component.html - 92 - - - src/app/components/manage/management-list/management-list.component.html - 92 - - - src/app/components/manage/management-list/management-list.component.html - 92 - - - src/app/components/manage/management-list/management-list.component.ts - 205 - - - src/app/components/manage/settings/settings.component.html - 298 - - - src/app/components/manage/settings/settings.component.html - 347 - - - src/app/components/manage/settings/settings.component.html - 385 - - - src/app/components/manage/settings/settings.component.html - 437 - - - src/app/components/manage/settings/settings.component.html - 472 - - Move to specified folder @@ -2182,21 +3213,6 @@ 23 - - Groups - - src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.html - 27 - - - src/app/components/manage/settings/settings.component.html - 424 - - - src/app/components/manage/settings/settings.component.html - 445 - - Create new user account @@ -2324,17 +3340,6 @@ 19 - - Objects without an owner can be viewed and edited by all users - - src/app/components/common/input/permissions/permissions-form/permissions-form.component.html - 25 - - - src/app/components/manage/settings/settings.component.html - 150 - - View @@ -2350,77 +3355,6 @@ 56 - - Edit - - src/app/components/common/input/permissions/permissions-form/permissions-form.component.html - 46 - - - src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html - 17 - - - src/app/components/document-list/document-card-large/document-card-large.component.html - 49 - - - src/app/components/document-list/document-card-small/document-card-small.component.html - 83 - - - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.html - 26 - - - src/app/components/manage/management-list/management-list.component.html - 73 - - - src/app/components/manage/management-list/management-list.component.html - 73 - - - src/app/components/manage/management-list/management-list.component.html - 73 - - - src/app/components/manage/management-list/management-list.component.html - 73 - - - src/app/components/manage/management-list/management-list.component.html - 87 - - - src/app/components/manage/management-list/management-list.component.html - 87 - - - src/app/components/manage/management-list/management-list.component.html - 87 - - - src/app/components/manage/management-list/management-list.component.html - 87 - - - src/app/components/manage/settings/settings.component.html - 345 - - - src/app/components/manage/settings/settings.component.html - 383 - - - src/app/components/manage/settings/settings.component.html - 436 - - - src/app/components/manage/settings/settings.component.html - 471 - - Add item @@ -2448,21 +3382,6 @@ 80 - - - - src/app/components/common/input/tags/tags.component.html - 2 - - - src/app/components/common/permissions-select/permissions-select.component.html - 22 - - - src/app/components/manage/tasks/tasks.component.html - 11 - - Add tag @@ -2519,17 +3438,6 @@ 48 - - Users - - src/app/components/common/permissions-filter-dropdown/permissions-filter-dropdown.component.html - 68 - - - src/app/components/manage/settings/settings.component.html - 410 - - Hide unowned @@ -2755,29 +3663,6 @@ 27 - - Created - - src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html - 9 - - - src/app/components/document-list/document-list.component.html - 184 - - - src/app/components/document-list/filter-editor/filter-editor.component.html - 62 - - - src/app/components/manage/tasks/tasks.component.html - 41 - - - src/app/services/rest/document.service.ts - 22 - - Title @@ -3017,61 +3902,6 @@ 24 - - Actions - - src/app/components/document-detail/document-detail.component.html - 34 - - - src/app/components/document-list/bulk-editor/bulk-editor.component.html - 86 - - - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.html - 16 - - - src/app/components/manage/management-list/management-list.component.html - 39 - - - src/app/components/manage/management-list/management-list.component.html - 39 - - - src/app/components/manage/management-list/management-list.component.html - 39 - - - src/app/components/manage/management-list/management-list.component.html - 39 - - - src/app/components/manage/settings/settings.component.html - 297 - - - src/app/components/manage/settings/settings.component.html - 335 - - - src/app/components/manage/settings/settings.component.html - 373 - - - src/app/components/manage/settings/settings.component.html - 425 - - - src/app/components/manage/settings/settings.component.html - 460 - - - src/app/components/manage/tasks/tasks.component.html - 44 - - Redo OCR @@ -3446,72 +4276,6 @@ 655 - - This operation cannot be undone. - - src/app/components/document-detail/document-detail.component.ts - 656 - - - src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 461 - - - src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 500 - - - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.ts - 91 - - - src/app/components/manage/settings/settings.component.ts - 801 - - - src/app/components/manage/settings/settings.component.ts - 853 - - - src/app/components/manage/settings/settings.component.ts - 914 - - - src/app/components/manage/settings/settings.component.ts - 976 - - - - Proceed - - src/app/components/document-detail/document-detail.component.ts - 658 - - - src/app/components/document-list/bulk-editor/bulk-editor.component.ts - 502 - - - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.ts - 93 - - - src/app/components/manage/settings/settings.component.ts - 803 - - - src/app/components/manage/settings/settings.component.ts - 855 - - - src/app/components/manage/settings/settings.component.ts - 916 - - - src/app/components/manage/settings/settings.component.ts - 978 - - Redo OCR operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content. @@ -3941,17 +4705,6 @@ 38 - - Views - - src/app/components/document-list/document-list.component.html - 64 - - - src/app/components/manage/settings/settings.component.html - 275 - - Save "" @@ -4066,21 +4819,6 @@ 159 - - Notes - - src/app/components/document-list/document-list.component.html - 163 - - - src/app/components/manage/settings/settings.component.html - 235 - - - src/app/services/rest/document.service.ts - 25 - - Sort by document type @@ -4306,28 +5044,6 @@ 3 - - Show in sidebar - - src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html - 9 - - - src/app/components/manage/settings/settings.component.html - 292 - - - - Show on dashboard - - src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html - 10 - - - src/app/components/manage/settings/settings.component.html - 288 - - Filter rules error occurred while saving this view @@ -4391,63 +5107,63 @@ Add Template - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.html + src/app/components/manage/consumption-templates/consumption-templates.component.html 6 Document Sources - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.html - 15 + src/app/components/manage/consumption-templates/consumption-templates.component.html + 16 No templates defined. - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.html - 33 + src/app/components/manage/consumption-templates/consumption-templates.component.html + 36 Saved template "". - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.ts + src/app/components/manage/consumption-templates/consumption-templates.component.ts 73 Error saving template. - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.ts + src/app/components/manage/consumption-templates/consumption-templates.component.ts 81 Confirm delete template - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.ts + src/app/components/manage/consumption-templates/consumption-templates.component.ts 89 This operation will permanently delete this template. - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.ts + src/app/components/manage/consumption-templates/consumption-templates.component.ts 90 Deleted template - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.ts + src/app/components/manage/consumption-templates/consumption-templates.component.ts 99 Error deleting template. - src/app/components/manage/consumption-templates-list/consumption-templates-list.component.ts + src/app/components/manage/consumption-templates/consumption-templates.component.ts 104 @@ -4500,29 +5216,178 @@ 44 - - Clear selection + + Mail Settings - src/app/components/manage/management-list/management-list.component.html - 5 + src/app/components/manage/mail/mail.component.html + 1 + + + Mail accounts - src/app/components/manage/management-list/management-list.component.html - 5 - - - src/app/components/manage/management-list/management-list.component.html - 5 - - - src/app/components/manage/management-list/management-list.component.html - 5 - - - src/app/components/manage/tasks/tasks.component.html + src/app/components/manage/mail/mail.component.html 6 + + Add Account + + src/app/components/manage/mail/mail.component.html + 11 + + + + Server + + src/app/components/manage/mail/mail.component.html + 19 + + + + No mail accounts defined. + + src/app/components/manage/mail/mail.component.html + 38 + + + + Mail rules + + src/app/components/manage/mail/mail.component.html + 44 + + + + Add Rule + + src/app/components/manage/mail/mail.component.html + 49 + + + + No mail rules defined. + + src/app/components/manage/mail/mail.component.html + 76 + + + + Error retrieving mail accounts + + src/app/components/manage/mail/mail.component.ts + 56 + + + + Error retrieving mail rules + + src/app/components/manage/mail/mail.component.ts + 70 + + + + Saved account "". + + src/app/components/manage/mail/mail.component.ts + 92 + + + + Error saving account. + + src/app/components/manage/mail/mail.component.ts + 104 + + + + Confirm delete mail account + + src/app/components/manage/mail/mail.component.ts + 112 + + + + This operation will permanently delete this mail account. + + src/app/components/manage/mail/mail.component.ts + 113 + + + + Deleted mail account + + src/app/components/manage/mail/mail.component.ts + 122 + + + + Error deleting mail account. + + src/app/components/manage/mail/mail.component.ts + 132 + + + + Saved rule "". + + src/app/components/manage/mail/mail.component.ts + 152 + + + + Error saving rule. + + src/app/components/manage/mail/mail.component.ts + 163 + + + + Confirm delete mail rule + + src/app/components/manage/mail/mail.component.ts + 171 + + + + This operation will permanently delete this mail rule. + + src/app/components/manage/mail/mail.component.ts + 172 + + + + Deleted mail rule + + src/app/components/manage/mail/mail.component.ts + 181 + + + + Error deleting mail rule. + + src/app/components/manage/mail/mail.component.ts + 190 + + + + Permissions updated + + src/app/components/manage/mail/mail.component.ts + 212 + + + + Error updating permissions + + src/app/components/manage/mail/mail.component.ts + 216 + + + src/app/components/manage/management-list/management-list.component.ts + 300 + + Filter by: @@ -4689,689 +5554,6 @@ 293 - - Error updating permissions - - src/app/components/manage/management-list/management-list.component.ts - 300 - - - src/app/components/manage/settings/settings.component.ts - 1020 - - - - Start tour - - src/app/components/manage/settings/settings.component.html - 2 - - - - Open Django Admin - - src/app/components/manage/settings/settings.component.html - 4 - - - - General - - src/app/components/manage/settings/settings.component.html - 15 - - - - Appearance - - src/app/components/manage/settings/settings.component.html - 18 - - - - Display language - - src/app/components/manage/settings/settings.component.html - 22 - - - - You need to reload the page after applying a new language. - - src/app/components/manage/settings/settings.component.html - 30 - - - - Date display - - src/app/components/manage/settings/settings.component.html - 37 - - - - Date format - - src/app/components/manage/settings/settings.component.html - 50 - - - - Short: - - src/app/components/manage/settings/settings.component.html - 56,57 - - - - Medium: - - src/app/components/manage/settings/settings.component.html - 60,61 - - - - Long: - - src/app/components/manage/settings/settings.component.html - 64,65 - - - - Items per page - - src/app/components/manage/settings/settings.component.html - 72 - - - - Document editor - - src/app/components/manage/settings/settings.component.html - 88 - - - - Use PDF viewer provided by the browser - - src/app/components/manage/settings/settings.component.html - 92 - - - - This is usually faster for displaying large PDF documents, but it might not work on some browsers. - - src/app/components/manage/settings/settings.component.html - 92 - - - - Sidebar - - src/app/components/manage/settings/settings.component.html - 99 - - - - Use 'slim' sidebar (icons only) - - src/app/components/manage/settings/settings.component.html - 103 - - - - Dark mode - - src/app/components/manage/settings/settings.component.html - 110 - - - - Use system settings - - src/app/components/manage/settings/settings.component.html - 113 - - - - Enable dark mode - - src/app/components/manage/settings/settings.component.html - 114 - - - - Invert thumbnails in dark mode - - src/app/components/manage/settings/settings.component.html - 115 - - - - Theme Color - - src/app/components/manage/settings/settings.component.html - 121 - - - - Reset - - src/app/components/manage/settings/settings.component.html - 130 - - - - Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI - - src/app/components/manage/settings/settings.component.html - 139,141 - - - - Default Owner - - src/app/components/manage/settings/settings.component.html - 146 - - - - Default View Permissions - - src/app/components/manage/settings/settings.component.html - 155 - - - - Default Edit Permissions - - src/app/components/manage/settings/settings.component.html - 182 - - - - Update checking - - src/app/components/manage/settings/settings.component.html - 211 - - - - Update checking works by pinging the public GitHub API for the latest release to determine whether a new version is available. Actual updating of the app must still be performed manually. - - src/app/components/manage/settings/settings.component.html - 215,218 - - - - No tracking data is collected by the app in any way. - - src/app/components/manage/settings/settings.component.html - 220,222 - - - - Enable update checking - - src/app/components/manage/settings/settings.component.html - 222 - - - - Note that for users of third-party containers e.g. linuxserver.io this notification may be 'ahead' of the current third-party release. - - src/app/components/manage/settings/settings.component.html - 222 - - - - Bulk editing - - src/app/components/manage/settings/settings.component.html - 226 - - - - Show confirmation dialogs - - src/app/components/manage/settings/settings.component.html - 230 - - - - Deleting documents will always ask for confirmation. - - src/app/components/manage/settings/settings.component.html - 230 - - - - Apply on close - - src/app/components/manage/settings/settings.component.html - 231 - - - - Enable notes - - src/app/components/manage/settings/settings.component.html - 239 - - - - Notifications - - src/app/components/manage/settings/settings.component.html - 247 - - - - Document processing - - src/app/components/manage/settings/settings.component.html - 250 - - - - Show notifications when new documents are detected - - src/app/components/manage/settings/settings.component.html - 254 - - - - Show notifications when document processing completes successfully - - src/app/components/manage/settings/settings.component.html - 255 - - - - Show notifications when document processing fails - - src/app/components/manage/settings/settings.component.html - 256 - - - - Suppress notifications on dashboard - - src/app/components/manage/settings/settings.component.html - 257 - - - - This will suppress all messages about document processing status on the dashboard. - - src/app/components/manage/settings/settings.component.html - 257 - - - - Show warning when closing saved views with unsaved changes - - src/app/components/manage/settings/settings.component.html - 271 - - - -  Appears on - - src/app/components/manage/settings/settings.component.html - 285,286 - - - - No saved views defined. - - src/app/components/manage/settings/settings.component.html - 302 - - - - Mail - - src/app/components/manage/settings/settings.component.html - 315 - - - - Mail accounts - - src/app/components/manage/settings/settings.component.html - 321 - - - - Add Account - - src/app/components/manage/settings/settings.component.html - 326 - - - - Server - - src/app/components/manage/settings/settings.component.html - 334 - - - - No mail accounts defined. - - src/app/components/manage/settings/settings.component.html - 353 - - - - Mail rules - - src/app/components/manage/settings/settings.component.html - 359 - - - - Add Rule - - src/app/components/manage/settings/settings.component.html - 364 - - - - No mail rules defined. - - src/app/components/manage/settings/settings.component.html - 391 - - - - Users & Groups - - src/app/components/manage/settings/settings.component.html - 405 - - - - Add User - - src/app/components/manage/settings/settings.component.html - 415 - - - - Add Group - - src/app/components/manage/settings/settings.component.html - 450 - - - - Error retrieving groups - - src/app/components/manage/settings/settings.component.ts - 304 - - - - Error retrieving mail rules - - src/app/components/manage/settings/settings.component.ts - 328 - - - - Error retrieving mail accounts - - src/app/components/manage/settings/settings.component.ts - 336 - - - - Error retrieving users - - src/app/components/manage/settings/settings.component.ts - 354 - - - - Saved view "" deleted. - - src/app/components/manage/settings/settings.component.ts - 558 - - - - Settings saved - - src/app/components/manage/settings/settings.component.ts - 680 - - - - Settings were saved successfully. - - src/app/components/manage/settings/settings.component.ts - 681 - - - - Settings were saved successfully. Reload is required to apply some changes. - - src/app/components/manage/settings/settings.component.ts - 685 - - - - Reload now - - src/app/components/manage/settings/settings.component.ts - 686 - - - - Use system language - - src/app/components/manage/settings/settings.component.ts - 704 - - - - Use date format of display language - - src/app/components/manage/settings/settings.component.ts - 711 - - - - Error while storing settings on server. - - src/app/components/manage/settings/settings.component.ts - 731 - - - - Password has been changed, you will be logged out momentarily. - - src/app/components/manage/settings/settings.component.ts - 773 - - - - Saved user "". - - src/app/components/manage/settings/settings.component.ts - 780 - - - - Error saving user. - - src/app/components/manage/settings/settings.component.ts - 791 - - - - Confirm delete user account - - src/app/components/manage/settings/settings.component.ts - 799 - - - - This operation will permanently delete this user account. - - src/app/components/manage/settings/settings.component.ts - 800 - - - - Deleted user - - src/app/components/manage/settings/settings.component.ts - 809 - - - - Error deleting user. - - src/app/components/manage/settings/settings.component.ts - 816 - - - - Saved group "". - - src/app/components/manage/settings/settings.component.ts - 834 - - - - Error saving group. - - src/app/components/manage/settings/settings.component.ts - 843 - - - - Confirm delete user group - - src/app/components/manage/settings/settings.component.ts - 851 - - - - This operation will permanently delete this user group. - - src/app/components/manage/settings/settings.component.ts - 852 - - - - Deleted group - - src/app/components/manage/settings/settings.component.ts - 861 - - - - Error deleting group. - - src/app/components/manage/settings/settings.component.ts - 868 - - - - Saved account "". - - src/app/components/manage/settings/settings.component.ts - 891 - - - - Error saving account. - - src/app/components/manage/settings/settings.component.ts - 904 - - - - Confirm delete mail account - - src/app/components/manage/settings/settings.component.ts - 912 - - - - This operation will permanently delete this mail account. - - src/app/components/manage/settings/settings.component.ts - 913 - - - - Deleted mail account - - src/app/components/manage/settings/settings.component.ts - 922 - - - - Error deleting mail account. - - src/app/components/manage/settings/settings.component.ts - 933 - - - - Saved rule "". - - src/app/components/manage/settings/settings.component.ts - 953 - - - - Error saving rule. - - src/app/components/manage/settings/settings.component.ts - 966 - - - - Confirm delete mail rule - - src/app/components/manage/settings/settings.component.ts - 974 - - - - This operation will permanently delete this mail rule. - - src/app/components/manage/settings/settings.component.ts - 975 - - - - Deleted mail rule - - src/app/components/manage/settings/settings.component.ts - 984 - - - - Error deleting mail rule. - - src/app/components/manage/settings/settings.component.ts - 994 - - - - Permissions updated - - src/app/components/manage/settings/settings.component.ts - 1016 - - storage path @@ -5414,140 +5596,6 @@ 53 - - Refresh - - src/app/components/manage/tasks/tasks.component.html - 20 - - - - Results - - src/app/components/manage/tasks/tasks.component.html - 42 - - - - click for full output - - src/app/components/manage/tasks/tasks.component.html - 66 - - - - Dismiss - - src/app/components/manage/tasks/tasks.component.html - 81 - - - src/app/components/manage/tasks/tasks.component.ts - 64 - - - - Open Document - - src/app/components/manage/tasks/tasks.component.html - 87 - - - - {VAR_PLURAL, plural, =1 {One task} other { total tasks}} - - src/app/components/manage/tasks/tasks.component.html - 103 - - - - Failed - - src/app/components/manage/tasks/tasks.component.html - 110 - - - - Complete - - src/app/components/manage/tasks/tasks.component.html - 116 - - - - Started - - src/app/components/manage/tasks/tasks.component.html - 122 - - - - Queued - - src/app/components/manage/tasks/tasks.component.html - 128 - - - - Dismiss selected - - src/app/components/manage/tasks/tasks.component.ts - 28 - - - - Dismiss all - - src/app/components/manage/tasks/tasks.component.ts - 29 - - - src/app/components/manage/tasks/tasks.component.ts - 62 - - - - Confirm Dismiss All - - src/app/components/manage/tasks/tasks.component.ts - 60 - - - - tasks? - - src/app/components/manage/tasks/tasks.component.ts - 62 - - - - queued - - src/app/components/manage/tasks/tasks.component.ts - 130 - - - - started - - src/app/components/manage/tasks/tasks.component.ts - 132 - - - - completed - - src/app/components/manage/tasks/tasks.component.ts - 134 - - - - failed - - src/app/components/manage/tasks/tasks.component.ts - 136 - - Not Found diff --git a/src-ui/src/app/app-routing.module.ts b/src-ui/src/app/app-routing.module.ts index 34bf77609..f2888b596 100644 --- a/src-ui/src/app/app-routing.module.ts +++ b/src-ui/src/app/app-routing.module.ts @@ -6,14 +6,14 @@ import { DocumentDetailComponent } from './components/document-detail/document-d import { DocumentListComponent } from './components/document-list/document-list.component' import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component' import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component' -import { LogsComponent } from './components/manage/logs/logs.component' -import { SettingsComponent } from './components/manage/settings/settings.component' +import { LogsComponent } from './components/admin/logs/logs.component' +import { SettingsComponent } from './components/admin/settings/settings.component' import { TagListComponent } from './components/manage/tag-list/tag-list.component' import { NotFoundComponent } from './components/not-found/not-found.component' import { DocumentAsnComponent } from './components/document-asn/document-asn.component' import { DirtyFormGuard } from './guards/dirty-form.guard' import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component' -import { TasksComponent } from './components/manage/tasks/tasks.component' +import { TasksComponent } from './components/admin/tasks/tasks.component' import { PermissionsGuard } from './guards/permissions.guard' import { DirtyDocGuard } from './guards/dirty-doc.guard' import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard' @@ -21,7 +21,9 @@ import { PermissionAction, PermissionType, } from './services/permissions.service' -import { ConsumptionTemplatesListComponent } from './components/manage/consumption-templates-list/consumption-templates-list.component' +import { ConsumptionTemplatesComponent } from './components/manage/consumption-templates/consumption-templates.component' +import { MailComponent } from './components/manage/mail/mail.component' +import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component' export const routes: Routes = [ { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, @@ -143,6 +145,15 @@ export const routes: Routes = [ }, }, }, + // redirect old paths + { + path: 'settings/mail', + redirectTo: '/mail', + }, + { + path: 'settings/usersgroups', + redirectTo: '/usersgroups', + }, { path: 'settings', component: SettingsComponent, @@ -167,11 +178,6 @@ export const routes: Routes = [ }, }, }, - { - path: 'settings/:section', - component: SettingsComponent, - canDeactivate: [DirtyFormGuard], - }, { path: 'tasks', component: TasksComponent, @@ -185,7 +191,7 @@ export const routes: Routes = [ }, { path: 'templates', - component: ConsumptionTemplatesListComponent, + component: ConsumptionTemplatesComponent, canActivate: [PermissionsGuard], data: { requiredPermission: { @@ -194,6 +200,28 @@ export const routes: Routes = [ }, }, }, + { + path: 'mail', + component: MailComponent, + canActivate: [PermissionsGuard], + data: { + requiredPermission: { + action: PermissionAction.View, + type: PermissionType.MailAccount, + }, + }, + }, + { + path: 'usersgroups', + component: UsersAndGroupsComponent, + canActivate: [PermissionsGuard], + data: { + requiredPermission: { + action: PermissionAction.View, + type: PermissionType.User, + }, + }, + }, ], }, diff --git a/src-ui/src/app/app.component.ts b/src-ui/src/app/app.component.ts index ca8e5ab23..9ca963337 100644 --- a/src-ui/src/app/app.component.ts +++ b/src-ui/src/app/app.component.ts @@ -179,6 +179,22 @@ export class AppComponent implements OnInit, OnDestroy { offset: 0, }, }, + { + anchorId: 'tour.mail', + content: $localize`Manage e-mail accounts and rules for automatically importing documents.`, + route: '/mail', + backdropConfig: { + offset: 0, + }, + }, + { + anchorId: 'tour.consumption-templates', + content: $localize`Consumption templates give you finer control over the document ingestion process.`, + route: '/templates', + backdropConfig: { + offset: 0, + }, + }, { 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.`, @@ -189,7 +205,7 @@ export class AppComponent implements OnInit, OnDestroy { }, { 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 and toggle settings for saved views.`, route: '/settings', backdropConfig: { offset: 0, diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index bb6c8777a..9d9307492 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -14,8 +14,8 @@ import { DashboardComponent } from './components/dashboard/dashboard.component' import { TagListComponent } from './components/manage/tag-list/tag-list.component' import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component' import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component' -import { LogsComponent } from './components/manage/logs/logs.component' -import { SettingsComponent } from './components/manage/settings/settings.component' +import { LogsComponent } from './components/admin/logs/logs.component' +import { SettingsComponent } from './components/admin/settings/settings.component' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { DatePipe, registerLocaleData } from '@angular/common' import { NotFoundComponent } from './components/not-found/not-found.component' @@ -77,7 +77,7 @@ import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard' import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component' import { StoragePathEditDialogComponent } from './components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component' import { SettingsService } from './services/settings.service' -import { TasksComponent } from './components/manage/tasks/tasks.component' +import { TasksComponent } from './components/admin/tasks/tasks.component' import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap' import { UserEditDialogComponent } from './components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component' import { GroupEditDialogComponent } from './components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component' @@ -95,8 +95,10 @@ import { UsernamePipe } from './pipes/username.pipe' import { LogoComponent } from './components/common/logo/logo.component' import { IsNumberPipe } from './pipes/is-number.pipe' import { ShareLinksDropdownComponent } from './components/common/share-links-dropdown/share-links-dropdown.component' -import { ConsumptionTemplatesListComponent } from './components/manage/consumption-templates-list/consumption-templates-list.component' +import { ConsumptionTemplatesComponent } from './components/manage/consumption-templates/consumption-templates.component' import { ConsumptionTemplateEditDialogComponent } from './components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component' +import { MailComponent } from './components/manage/mail/mail.component' +import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component' import localeAf from '@angular/common/locales/af' import localeAr from '@angular/common/locales/ar' @@ -235,8 +237,10 @@ function initializeApp(settings: SettingsService) { LogoComponent, IsNumberPipe, ShareLinksDropdownComponent, - ConsumptionTemplatesListComponent, + ConsumptionTemplatesComponent, ConsumptionTemplateEditDialogComponent, + MailComponent, + UsersAndGroupsComponent, ], imports: [ BrowserModule, diff --git a/src-ui/src/app/components/manage/logs/logs.component.html b/src-ui/src/app/components/admin/logs/logs.component.html similarity index 100% rename from src-ui/src/app/components/manage/logs/logs.component.html rename to src-ui/src/app/components/admin/logs/logs.component.html diff --git a/src-ui/src/app/components/manage/logs/logs.component.scss b/src-ui/src/app/components/admin/logs/logs.component.scss similarity index 100% rename from src-ui/src/app/components/manage/logs/logs.component.scss rename to src-ui/src/app/components/admin/logs/logs.component.scss diff --git a/src-ui/src/app/components/manage/logs/logs.component.spec.ts b/src-ui/src/app/components/admin/logs/logs.component.spec.ts similarity index 100% rename from src-ui/src/app/components/manage/logs/logs.component.spec.ts rename to src-ui/src/app/components/admin/logs/logs.component.spec.ts diff --git a/src-ui/src/app/components/manage/logs/logs.component.ts b/src-ui/src/app/components/admin/logs/logs.component.ts similarity index 100% rename from src-ui/src/app/components/manage/logs/logs.component.ts rename to src-ui/src/app/components/admin/logs/logs.component.ts diff --git a/src-ui/src/app/components/manage/settings/settings.component.html b/src-ui/src/app/components/admin/settings/settings.component.html similarity index 57% rename from src-ui/src/app/components/manage/settings/settings.component.html rename to src-ui/src/app/components/admin/settings/settings.component.html index e2e4ed5c5..447f9cfb7 100644 --- a/src-ui/src/app/components/manage/settings/settings.component.html +++ b/src-ui/src/app/components/admin/settings/settings.component.html @@ -11,7 +11,7 @@
diff --git a/src-ui/src/app/components/manage/settings/settings.component.scss b/src-ui/src/app/components/admin/settings/settings.component.scss similarity index 100% rename from src-ui/src/app/components/manage/settings/settings.component.scss rename to src-ui/src/app/components/admin/settings/settings.component.scss diff --git a/src-ui/src/app/components/admin/settings/settings.component.spec.ts b/src-ui/src/app/components/admin/settings/settings.component.spec.ts new file mode 100644 index 000000000..7ea025fe5 --- /dev/null +++ b/src-ui/src/app/components/admin/settings/settings.component.spec.ts @@ -0,0 +1,356 @@ +import { ViewportScroller, DatePipe } from '@angular/common' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { ComponentFixture, TestBed } from '@angular/core/testing' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { By } from '@angular/platform-browser' +import { Router, ActivatedRoute, convertToParamMap } from '@angular/router' +import { RouterTestingModule } from '@angular/router/testing' +import { + NgbModule, + NgbAlertModule, + NgbNavLink, +} 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 { PaperlessSavedView } from 'src/app/data/paperless-saved-view' +import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' +import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' +import { PermissionsGuard } from 'src/app/guards/permissions.guard' +import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' +import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' +import { PermissionsService } from 'src/app/services/permissions.service' +import { GroupService } from 'src/app/services/rest/group.service' +import { SavedViewService } from 'src/app/services/rest/saved-view.service' +import { UserService } from 'src/app/services/rest/user.service' +import { SettingsService } 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 { CheckComponent } from '../../common/input/check/check.component' +import { ColorComponent } from '../../common/input/color/color.component' +import { NumberComponent } from '../../common/input/number/number.component' +import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component' +import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component' +import { SelectComponent } from '../../common/input/select/select.component' +import { TagsComponent } from '../../common/input/tags/tags.component' +import { TextComponent } from '../../common/input/text/text.component' +import { PageHeaderComponent } from '../../common/page-header/page-header.component' +import { SettingsComponent } from './settings.component' +import { IfOwnerDirective } from 'src/app/directives/if-owner.directive' + +const savedViews = [ + { id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true }, + { id: 2, name: 'view2', show_in_sidebar: false, show_on_dashboard: false }, +] +const users = [ + { id: 1, username: 'user1', is_superuser: false }, + { id: 2, username: 'user2', is_superuser: false }, +] +const groups = [ + { id: 1, name: 'group1' }, + { id: 2, name: 'group2' }, +] + +describe('SettingsComponent', () => { + let component: SettingsComponent + let fixture: ComponentFixture + let router: Router + let settingsService: SettingsService + let savedViewService: SavedViewService + let activatedRoute: ActivatedRoute + let viewportScroller: ViewportScroller + let toastService: ToastService + let userService: UserService + let permissionsService: PermissionsService + let groupService: GroupService + + beforeEach(async () => { + TestBed.configureTestingModule({ + declarations: [ + SettingsComponent, + PageHeaderComponent, + IfPermissionsDirective, + CustomDatePipe, + ConfirmDialogComponent, + CheckComponent, + ColorComponent, + SafeHtmlPipe, + SelectComponent, + TextComponent, + NumberComponent, + TagsComponent, + PermissionsUserComponent, + PermissionsGroupComponent, + IfOwnerDirective, + ], + providers: [CustomDatePipe, DatePipe, PermissionsGuard], + imports: [ + NgbModule, + HttpClientTestingModule, + RouterTestingModule.withRoutes(routes), + FormsModule, + ReactiveFormsModule, + NgbAlertModule, + NgSelectModule, + ], + }).compileComponents() + + router = TestBed.inject(Router) + activatedRoute = TestBed.inject(ActivatedRoute) + viewportScroller = TestBed.inject(ViewportScroller) + toastService = TestBed.inject(ToastService) + settingsService = TestBed.inject(SettingsService) + settingsService.currentUser = users[0] + userService = TestBed.inject(UserService) + permissionsService = TestBed.inject(PermissionsService) + jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) + jest + .spyOn(permissionsService, 'currentUserHasObjectPermissions') + .mockReturnValue(true) + jest + .spyOn(permissionsService, 'currentUserOwnsObject') + .mockReturnValue(true) + groupService = TestBed.inject(GroupService) + savedViewService = TestBed.inject(SavedViewService) + }) + + function completeSetup(excludeService = null) { + if (excludeService !== userService) { + jest.spyOn(userService, 'listAll').mockReturnValue( + of({ + all: users.map((u) => u.id), + count: users.length, + results: users.concat([]), + }) + ) + } + if (excludeService !== groupService) { + jest.spyOn(groupService, 'listAll').mockReturnValue( + of({ + all: groups.map((g) => g.id), + count: groups.length, + results: groups.concat([]), + }) + ) + } + if (excludeService !== savedViewService) { + jest.spyOn(savedViewService, 'listAll').mockReturnValue( + of({ + all: savedViews.map((v) => v.id), + count: savedViews.length, + results: (savedViews as PaperlessSavedView[]).concat([]), + }) + ) + } + + fixture = TestBed.createComponent(SettingsComponent) + component = fixture.componentInstance + fixture.detectChanges() + } + + it('should support tabbed settings & change URL, prevent navigation if dirty confirmation rejected', () => { + completeSetup() + const navigateSpy = jest.spyOn(router, 'navigate') + const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) + tabButtons[1].nativeElement.dispatchEvent(new MouseEvent('click')) + expect(navigateSpy).toHaveBeenCalledWith(['settings', 'permissions']) + tabButtons[2].nativeElement.dispatchEvent(new MouseEvent('click')) + expect(navigateSpy).toHaveBeenCalledWith(['settings', 'notifications']) + tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) + expect(navigateSpy).toHaveBeenCalledWith(['settings', 'savedviews']) + + const initSpy = jest.spyOn(component, 'initialize') + component.isDirty = true // mock dirty + navigateSpy.mockResolvedValueOnce(false) // nav rejected cause dirty + tabButtons[0].nativeElement.dispatchEvent(new MouseEvent('click')) + expect(navigateSpy).toHaveBeenCalledWith(['settings', 'general']) + expect(initSpy).not.toHaveBeenCalled() + + navigateSpy.mockResolvedValueOnce(true) // nav accepted even though dirty + tabButtons[1].nativeElement.dispatchEvent(new MouseEvent('click')) + expect(navigateSpy).toHaveBeenCalledWith(['settings', 'notifications']) + expect(initSpy).toHaveBeenCalled() + }) + + it('should support direct link to tab by URL, scroll if needed', () => { + completeSetup() + jest + .spyOn(activatedRoute, 'paramMap', 'get') + .mockReturnValue(of(convertToParamMap({ section: 'notifications' }))) + activatedRoute.snapshot.fragment = '#notifications' + const scrollSpy = jest.spyOn(viewportScroller, 'scrollToAnchor') + component.ngOnInit() + expect(component.activeNavID).toEqual(3) // Users & Groups + component.ngAfterViewInit() + expect(scrollSpy).toHaveBeenCalledWith('#notifications') + }) + + it('should support save saved views, show error', () => { + completeSetup() + + const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) + tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) + fixture.detectChanges() + + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastSpy = jest.spyOn(toastService, 'show') + const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany') + + const toggle = fixture.debugElement.query( + By.css('.form-check.form-switch input') + ) + toggle.nativeElement.checked = true + toggle.nativeElement.dispatchEvent(new Event('change')) + + // saved views error first + savedViewPatchSpy.mockReturnValueOnce( + throwError(() => new Error('unable to save saved views')) + ) + component.saveSettings() + expect(toastErrorSpy).toHaveBeenCalled() + expect(savedViewPatchSpy).toHaveBeenCalled() + toastSpy.mockClear() + toastErrorSpy.mockClear() + savedViewPatchSpy.mockClear() + + // succeed saved views + savedViewPatchSpy.mockReturnValueOnce( + of(savedViews as PaperlessSavedView[]) + ) + component.saveSettings() + expect(toastErrorSpy).not.toHaveBeenCalled() + expect(savedViewPatchSpy).toHaveBeenCalled() + }) + + it('should update only patch saved views that have changed', () => { + completeSetup() + + const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) + tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) + fixture.detectChanges() + + const patchSpy = jest.spyOn(savedViewService, 'patchMany') + component.saveSettings() + expect(patchSpy).not.toHaveBeenCalled() + + const view = savedViews[0] + const toggle = fixture.debugElement.query( + By.css('.form-check.form-switch input') + ) + toggle.nativeElement.checked = true + toggle.nativeElement.dispatchEvent(new Event('change')) + // register change + component.savedViewGroup.get(view.id.toString()).value[ + 'show_on_dashboard' + ] = !view.show_on_dashboard + fixture.detectChanges() + + component.saveSettings() + expect(patchSpy).toHaveBeenCalledWith([ + { + id: view.id, + name: view.name, + show_in_sidebar: view.show_in_sidebar, + show_on_dashboard: !view.show_on_dashboard, + }, + ]) + }) + + it('should support save local settings updating appearance settings and calling API, show error', () => { + completeSetup() + jest.spyOn(savedViewService, 'patchMany').mockReturnValue(of([])) + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastSpy = jest.spyOn(toastService, 'show') + const storeSpy = jest.spyOn(settingsService, 'storeSettings') + const appearanceSettingsSpy = jest.spyOn( + settingsService, + 'updateAppearanceSettings' + ) + const setSpy = jest.spyOn(settingsService, 'set') + + // error first + storeSpy.mockReturnValueOnce( + throwError(() => new Error('unable to save settings')) + ) + component.saveSettings() + + expect(toastErrorSpy).toHaveBeenCalled() + expect(storeSpy).toHaveBeenCalled() + expect(appearanceSettingsSpy).not.toHaveBeenCalled() + expect(setSpy).toHaveBeenCalledTimes(24) + + // succeed + storeSpy.mockReturnValueOnce(of(true)) + component.saveSettings() + expect(toastSpy).toHaveBeenCalled() + expect(appearanceSettingsSpy).toHaveBeenCalled() + }) + + it('should offer reload if settings changes require', () => { + completeSetup() + jest.spyOn(savedViewService, 'patchMany').mockReturnValue(of([])) + let toast: Toast + toastService.getToasts().subscribe((t) => (toast = t[0])) + component.initialize(true) // reset + component.store.getValue()['displayLanguage'] = 'en-US' + component.store.getValue()['updateCheckingEnabled'] = false + component.settingsForm.value.displayLanguage = 'en-GB' + component.settingsForm.value.updateCheckingEnabled = true + jest.spyOn(settingsService, 'storeSettings').mockReturnValueOnce(of(true)) + component.saveSettings() + expect(toast.actionName).toEqual('Reload now') + }) + + it('should allow setting theme color, visually apply change immediately but not save', () => { + completeSetup() + const appearanceSpy = jest.spyOn( + settingsService, + 'updateAppearanceSettings' + ) + const colorInput = fixture.debugElement.query(By.directive(ColorComponent)) + colorInput.query(By.css('input')).nativeElement.value = '#ff0000' + colorInput + .query(By.css('input')) + .nativeElement.dispatchEvent(new Event('change')) + fixture.detectChanges() + expect(appearanceSpy).toHaveBeenCalled() + expect(settingsService.get(SETTINGS_KEYS.THEME_COLOR)).toEqual('') + component.clearThemeColor() + }) + + it('should support delete saved view', () => { + completeSetup() + const toastSpy = jest.spyOn(toastService, 'showInfo') + const deleteSpy = jest.spyOn(savedViewService, 'delete') + deleteSpy.mockReturnValue(of(true)) + component.deleteSavedView(savedViews[0] as PaperlessSavedView) + expect(deleteSpy).toHaveBeenCalled() + expect(toastSpy).toHaveBeenCalledWith( + `Saved view "${savedViews[0].name}" deleted.` + ) + }) + + it('should show errors on load if load users failure', () => { + const toastErrorSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(userService, 'listAll') + .mockImplementation(() => + throwError(() => new Error('failed to load users')) + ) + completeSetup(userService) + fixture.detectChanges() + expect(toastErrorSpy).toBeCalled() + }) + + it('should show errors on load if load groups failure', () => { + const toastErrorSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(groupService, 'listAll') + .mockImplementation(() => + throwError(() => new Error('failed to load groups')) + ) + completeSetup(groupService) + fixture.detectChanges() + expect(toastErrorSpy).toBeCalled() + }) +}) diff --git a/src-ui/src/app/components/admin/settings/settings.component.ts b/src-ui/src/app/components/admin/settings/settings.component.ts new file mode 100644 index 000000000..14528d76d --- /dev/null +++ b/src-ui/src/app/components/admin/settings/settings.component.ts @@ -0,0 +1,551 @@ +import { ViewportScroller } from '@angular/common' +import { + Component, + OnInit, + AfterViewInit, + OnDestroy, + Inject, + LOCALE_ID, +} from '@angular/core' +import { FormGroup, FormControl } from '@angular/forms' +import { ActivatedRoute, Router } from '@angular/router' +import { NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap' +import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms' +import { TourService } from 'ngx-ui-tour-ng-bootstrap' +import { + BehaviorSubject, + Subscription, + Observable, + Subject, + first, + takeUntil, + tap, +} from 'rxjs' +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, + PermissionType, +} from 'src/app/services/permissions.service' +import { GroupService } from 'src/app/services/rest/group.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 { ComponentWithPermissions } from '../../with-permissions/with-permissions.component' + +enum SettingsNavIDs { + General = 1, + Permissions = 2, + Notifications = 3, + SavedViews = 4, +} + +@Component({ + selector: 'pngx-settings', + templateUrl: './settings.component.html', + styleUrls: ['./settings.component.scss'], +}) +export class SettingsComponent + extends ComponentWithPermissions + implements OnInit, AfterViewInit, OnDestroy, DirtyComponent +{ + SettingsNavIDs = SettingsNavIDs + activeNavID: number + + savedViewGroup = new FormGroup({}) + + settingsForm = new FormGroup({ + bulkEditConfirmationDialogs: new FormControl(null), + bulkEditApplyOnClose: new FormControl(null), + documentListItemPerPage: new FormControl(null), + slimSidebarEnabled: new FormControl(null), + darkModeUseSystem: new FormControl(null), + darkModeEnabled: new FormControl(null), + darkModeInvertThumbs: new FormControl(null), + themeColor: new FormControl(null), + useNativePdfViewer: new FormControl(null), + displayLanguage: new FormControl(null), + dateLocale: new FormControl(null), + dateFormat: new FormControl(null), + notesEnabled: new FormControl(null), + updateCheckingEnabled: new FormControl(null), + defaultPermsOwner: new FormControl(null), + defaultPermsViewUsers: new FormControl(null), + defaultPermsViewGroups: new FormControl(null), + defaultPermsEditUsers: new FormControl(null), + defaultPermsEditGroups: new FormControl(null), + + notificationsConsumerNewDocument: new FormControl(null), + notificationsConsumerSuccess: new FormControl(null), + notificationsConsumerFailed: new FormControl(null), + notificationsConsumerSuppressOnDashboard: new FormControl(null), + + savedViewsWarnOnUnsavedChange: new FormControl(null), + savedViews: this.savedViewGroup, + }) + + savedViews: PaperlessSavedView[] + + store: BehaviorSubject + storeSub: Subscription + isDirty$: Observable + isDirty: boolean = false + unsubscribeNotifier: Subject = new Subject() + savePending: boolean = false + + users: PaperlessUser[] + groups: PaperlessGroup[] + + get computedDateLocale(): string { + return ( + this.settingsForm.value.dateLocale || + this.settingsForm.value.displayLanguage || + this.currentLocale + ) + } + + constructor( + public savedViewService: SavedViewService, + private documentListViewService: DocumentListViewService, + private toastService: ToastService, + private settings: SettingsService, + @Inject(LOCALE_ID) public currentLocale: string, + private viewportScroller: ViewportScroller, + private activatedRoute: ActivatedRoute, + public readonly tourService: TourService, + private usersService: UserService, + private groupsService: GroupService, + private router: Router, + public permissionsService: PermissionsService + ) { + super() + this.settings.settingsSaved.subscribe(() => { + if (!this.savePending) this.initialize() + }) + } + + ngOnInit() { + this.initialize() + + if ( + this.permissionsService.currentUserCan( + PermissionAction.View, + PermissionType.User + ) + ) { + this.usersService + .listAll() + .pipe(first()) + .subscribe({ + next: (r) => { + this.users = r.results + }, + 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 + }, + 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) => { + const section = paramMap.get('section') + if (section) { + const navIDKey: string = Object.keys(SettingsNavIDs).find( + (navID) => navID.toLowerCase() == section + ) + if (navIDKey) { + this.activeNavID = SettingsNavIDs[navIDKey] + } + } + }) + } + + ngAfterViewInit(): void { + if (this.activatedRoute.snapshot.fragment) { + this.viewportScroller.scrollToAnchor( + this.activatedRoute.snapshot.fragment + ) + } + } + + private getCurrentSettings() { + return { + bulkEditConfirmationDialogs: this.settings.get( + SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS + ), + bulkEditApplyOnClose: this.settings.get( + SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE + ), + documentListItemPerPage: this.settings.get( + SETTINGS_KEYS.DOCUMENT_LIST_SIZE + ), + slimSidebarEnabled: this.settings.get(SETTINGS_KEYS.SLIM_SIDEBAR), + darkModeUseSystem: this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM), + darkModeEnabled: this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED), + darkModeInvertThumbs: this.settings.get( + SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED + ), + themeColor: this.settings.get(SETTINGS_KEYS.THEME_COLOR), + useNativePdfViewer: this.settings.get( + SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER + ), + displayLanguage: this.settings.getLanguage(), + dateLocale: this.settings.get(SETTINGS_KEYS.DATE_LOCALE), + dateFormat: this.settings.get(SETTINGS_KEYS.DATE_FORMAT), + notesEnabled: this.settings.get(SETTINGS_KEYS.NOTES_ENABLED), + updateCheckingEnabled: this.settings.get( + SETTINGS_KEYS.UPDATE_CHECKING_ENABLED + ), + notificationsConsumerNewDocument: this.settings.get( + SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT + ), + notificationsConsumerSuccess: this.settings.get( + SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS + ), + notificationsConsumerFailed: this.settings.get( + SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED + ), + notificationsConsumerSuppressOnDashboard: this.settings.get( + SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD + ), + savedViewsWarnOnUnsavedChange: this.settings.get( + SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE + ), + defaultPermsOwner: this.settings.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER), + defaultPermsViewUsers: this.settings.get( + SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS + ), + defaultPermsViewGroups: this.settings.get( + SETTINGS_KEYS.DEFAULT_PERMS_VIEW_GROUPS + ), + defaultPermsEditUsers: this.settings.get( + SETTINGS_KEYS.DEFAULT_PERMS_EDIT_USERS + ), + defaultPermsEditGroups: this.settings.get( + SETTINGS_KEYS.DEFAULT_PERMS_EDIT_GROUPS + ), + savedViews: {}, + } + } + + onNavChange(navChangeEvent: NgbNavChangeEvent) { + const [foundNavIDkey] = Object.entries(SettingsNavIDs).find( + ([, navIDValue]) => navIDValue == navChangeEvent.nextId + ) + if (foundNavIDkey) + // if its dirty we need to wait for confirmation + this.router + .navigate(['settings', foundNavIDkey.toLowerCase()]) + .then((navigated) => { + if (!navigated && this.isDirty) { + this.activeNavID = navChangeEvent.activeId + } else if (navigated && this.isDirty) { + this.initialize() + } + }) + } + + initialize(resetSettings: boolean = true) { + this.unsubscribeNotifier.next(true) + + const currentFormValue = this.settingsForm.value + + let storeData = this.getCurrentSettings() + + if (this.savedViews) { + this.emptyGroup(this.savedViewGroup) + + for (let view of this.savedViews) { + storeData.savedViews[view.id.toString()] = { + id: view.id, + name: view.name, + show_on_dashboard: view.show_on_dashboard, + show_in_sidebar: view.show_in_sidebar, + } + this.savedViewGroup.addControl( + view.id.toString(), + new FormGroup({ + id: new FormControl(null), + name: new FormControl(null), + show_on_dashboard: new FormControl(null), + show_in_sidebar: new FormControl(null), + }) + ) + } + } + + this.store = new BehaviorSubject(storeData) + + this.storeSub = this.store.asObservable().subscribe((state) => { + this.settingsForm.patchValue(state, { emitEvent: false }) + }) + + // Initialize dirtyCheck + this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable()) + + // Record dirty in case we need to 'undo' appearance settings if not saved on close + this.isDirty$ + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe((dirty) => { + this.isDirty = dirty + }) + + // "Live" visual changes prior to save + this.settingsForm.valueChanges + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe(() => { + this.settings.updateAppearanceSettings( + this.settingsForm.get('darkModeUseSystem').value, + this.settingsForm.get('darkModeEnabled').value, + this.settingsForm.get('themeColor').value + ) + }) + + if (!resetSettings && currentFormValue) { + // prevents loss of unsaved changes + this.settingsForm.patchValue(currentFormValue) + } + } + + private emptyGroup(group: FormGroup) { + Object.keys(group.controls).forEach((key) => group.removeControl(key)) + } + + ngOnDestroy() { + if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didnt save + this.storeSub && this.storeSub.unsubscribe() + } + + deleteSavedView(savedView: PaperlessSavedView) { + this.savedViewService.delete(savedView).subscribe(() => { + this.savedViewGroup.removeControl(savedView.id.toString()) + this.savedViews.splice(this.savedViews.indexOf(savedView), 1) + this.toastService.showInfo( + $localize`Saved view "${savedView.name}" deleted.` + ) + this.savedViewService.clearCache() + this.savedViewService.listAll().subscribe((r) => { + this.savedViews = r.results + this.initialize(true) + }) + }) + } + + private saveLocalSettings() { + this.savePending = true + const reloadRequired = + this.settingsForm.value.displayLanguage != + this.store?.getValue()['displayLanguage'] || // displayLanguage is dirty + (this.settingsForm.value.updateCheckingEnabled != + this.store?.getValue()['updateCheckingEnabled'] && + this.settingsForm.value.updateCheckingEnabled) // update checking was turned on + + this.settings.set( + SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, + this.settingsForm.value.bulkEditApplyOnClose + ) + this.settings.set( + SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, + this.settingsForm.value.bulkEditConfirmationDialogs + ) + this.settings.set( + SETTINGS_KEYS.DOCUMENT_LIST_SIZE, + this.settingsForm.value.documentListItemPerPage + ) + this.settings.set( + SETTINGS_KEYS.SLIM_SIDEBAR, + this.settingsForm.value.slimSidebarEnabled + ) + this.settings.set( + SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, + this.settingsForm.value.darkModeUseSystem + ) + this.settings.set( + SETTINGS_KEYS.DARK_MODE_ENABLED, + (this.settingsForm.value.darkModeEnabled == true).toString() + ) + this.settings.set( + SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, + (this.settingsForm.value.darkModeInvertThumbs == true).toString() + ) + this.settings.set( + SETTINGS_KEYS.THEME_COLOR, + this.settingsForm.value.themeColor.toString() + ) + this.settings.set( + SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, + this.settingsForm.value.useNativePdfViewer + ) + this.settings.set( + SETTINGS_KEYS.DATE_LOCALE, + this.settingsForm.value.dateLocale + ) + this.settings.set( + SETTINGS_KEYS.DATE_FORMAT, + this.settingsForm.value.dateFormat + ) + this.settings.set( + SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT, + this.settingsForm.value.notificationsConsumerNewDocument + ) + this.settings.set( + SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS, + this.settingsForm.value.notificationsConsumerSuccess + ) + this.settings.set( + SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, + this.settingsForm.value.notificationsConsumerFailed + ) + this.settings.set( + SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, + this.settingsForm.value.notificationsConsumerSuppressOnDashboard + ) + this.settings.set( + SETTINGS_KEYS.NOTES_ENABLED, + this.settingsForm.value.notesEnabled + ) + this.settings.set( + SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, + this.settingsForm.value.updateCheckingEnabled + ) + this.settings.set( + SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE, + this.settingsForm.value.savedViewsWarnOnUnsavedChange + ) + this.settings.set( + SETTINGS_KEYS.DEFAULT_PERMS_OWNER, + this.settingsForm.value.defaultPermsOwner + ) + this.settings.set( + SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS, + this.settingsForm.value.defaultPermsViewUsers + ) + this.settings.set( + SETTINGS_KEYS.DEFAULT_PERMS_VIEW_GROUPS, + this.settingsForm.value.defaultPermsViewGroups + ) + this.settings.set( + SETTINGS_KEYS.DEFAULT_PERMS_EDIT_USERS, + this.settingsForm.value.defaultPermsEditUsers + ) + this.settings.set( + SETTINGS_KEYS.DEFAULT_PERMS_EDIT_GROUPS, + this.settingsForm.value.defaultPermsEditGroups + ) + this.settings.setLanguage(this.settingsForm.value.displayLanguage) + this.settings + .storeSettings() + .pipe(first()) + .pipe(tap(() => (this.savePending = false))) + .subscribe({ + next: () => { + this.store.next(this.settingsForm.value) + this.documentListViewService.updatePageSize() + this.settings.updateAppearanceSettings() + let savedToast: Toast = { + title: $localize`Settings saved`, + content: $localize`Settings were saved successfully.`, + delay: 5000, + } + if (reloadRequired) { + savedToast.content = $localize`Settings were saved successfully. Reload is required to apply some changes.` + savedToast.actionName = $localize`Reload now` + savedToast.action = () => { + location.reload() + } + } + + this.toastService.show(savedToast) + }, + error: (error) => { + this.toastService.showError( + $localize`An error occurred while saving settings.`, + error + ) + }, + }) + } + + get displayLanguageOptions(): LanguageOption[] { + return [{ code: '', name: $localize`Use system language` }].concat( + this.settings.getLanguageOptions() + ) + } + + get dateLocaleOptions(): LanguageOption[] { + return [ + { code: '', name: $localize`Use date format of display language` }, + ].concat(this.settings.getDateLocaleOptions()) + } + + get today() { + return new Date() + } + + saveSettings() { + // only patch views that have actually changed + const changed: PaperlessSavedView[] = [] + Object.values(this.savedViewGroup.controls) + .filter((g: FormGroup) => !g.pristine) + .forEach((group: FormGroup) => { + changed.push(group.value) + }) + if (changed.length > 0) { + this.savedViewService.patchMany(changed).subscribe({ + next: () => { + this.saveLocalSettings() + }, + error: (error) => { + this.toastService.showError( + $localize`Error while storing settings on server.`, + error + ) + }, + }) + } else { + this.saveLocalSettings() + } + } + + clearThemeColor() { + this.settingsForm.get('themeColor').patchValue('') + } +} diff --git a/src-ui/src/app/components/manage/tasks/tasks.component.html b/src-ui/src/app/components/admin/tasks/tasks.component.html similarity index 100% rename from src-ui/src/app/components/manage/tasks/tasks.component.html rename to src-ui/src/app/components/admin/tasks/tasks.component.html diff --git a/src-ui/src/app/components/manage/tasks/tasks.component.scss b/src-ui/src/app/components/admin/tasks/tasks.component.scss similarity index 100% rename from src-ui/src/app/components/manage/tasks/tasks.component.scss rename to src-ui/src/app/components/admin/tasks/tasks.component.scss diff --git a/src-ui/src/app/components/manage/tasks/tasks.component.spec.ts b/src-ui/src/app/components/admin/tasks/tasks.component.spec.ts similarity index 100% rename from src-ui/src/app/components/manage/tasks/tasks.component.spec.ts rename to src-ui/src/app/components/admin/tasks/tasks.component.spec.ts diff --git a/src-ui/src/app/components/manage/tasks/tasks.component.ts b/src-ui/src/app/components/admin/tasks/tasks.component.ts similarity index 100% rename from src-ui/src/app/components/manage/tasks/tasks.component.ts rename to src-ui/src/app/components/admin/tasks/tasks.component.ts diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.html b/src-ui/src/app/components/admin/users-groups/users-groups.component.html new file mode 100644 index 000000000..daea4cb2f --- /dev/null +++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.html @@ -0,0 +1,99 @@ + + + + +

+ Users + +

+
    + +
  • +
    +
    Username
    +
    Name
    +
    Groups
    +
    Actions
    +
    +
  • + +
  • +
    +
    +
    {{user.first_name}} {{user.last_name}}
    +
    {{user.groups?.map(getGroupName, this).join(', ')}}
    +
    +
    + + +
    +
    +
    +
  • +
+
+ + +

+ Groups + +

+
    + +
  • +
    +
    Name
    +
    +
    +
    Actions
    +
    +
  • + +
  • +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
  • +
+ +
No groups defined
+
+ +
+
+
Loading...
+
diff --git a/src-ui/src/app/components/manage/consumption-templates-list/consumption-templates-list.component.scss b/src-ui/src/app/components/admin/users-groups/users-groups.component.scss similarity index 100% rename from src-ui/src/app/components/manage/consumption-templates-list/consumption-templates-list.component.scss rename to src-ui/src/app/components/admin/users-groups/users-groups.component.scss diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts b/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts new file mode 100644 index 000000000..fd8961d51 --- /dev/null +++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.spec.ts @@ -0,0 +1,267 @@ +import { DatePipe } from '@angular/common' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { + ComponentFixture, + TestBed, + fakeAsync, + tick, +} from '@angular/core/testing' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { RouterTestingModule } from '@angular/router/testing' +import { + NgbModule, + NgbAlertModule, + NgbModal, + NgbModalRef, +} from '@ng-bootstrap/ng-bootstrap' +import { NgSelectModule } from '@ng-select/ng-select' +import { throwError, of } from 'rxjs' +import { routes } from 'src/app/app-routing.module' +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 { PermissionsService } from 'src/app/services/permissions.service' +import { GroupService } from 'src/app/services/rest/group.service' +import { UserService } from 'src/app/services/rest/user.service' +import { SettingsService } from 'src/app/services/settings.service' +import { ToastService } from 'src/app/services/toast.service' +import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-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 { CheckComponent } from '../../common/input/check/check.component' +import { NumberComponent } from '../../common/input/number/number.component' +import { PasswordComponent } from '../../common/input/password/password.component' +import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component' +import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component' +import { SelectComponent } from '../../common/input/select/select.component' +import { TagsComponent } from '../../common/input/tags/tags.component' +import { TextComponent } from '../../common/input/text/text.component' +import { PageHeaderComponent } from '../../common/page-header/page-header.component' +import { SettingsComponent } from '../settings/settings.component' +import { UsersAndGroupsComponent } from './users-groups.component' +import { PaperlessUser } from 'src/app/data/paperless-user' +import { PaperlessGroup } from 'src/app/data/paperless-group' + +const users = [ + { id: 1, username: 'user1', is_superuser: false }, + { id: 2, username: 'user2', is_superuser: false }, +] +const groups = [ + { id: 1, name: 'group1' }, + { id: 2, name: 'group2' }, +] + +describe('UsersAndGroupsComponent', () => { + let component: UsersAndGroupsComponent + let fixture: ComponentFixture + let settingsService: SettingsService + let modalService: NgbModal + let toastService: ToastService + let userService: UserService + let permissionsService: PermissionsService + let groupService: GroupService + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ + UsersAndGroupsComponent, + SettingsComponent, + PageHeaderComponent, + IfPermissionsDirective, + CustomDatePipe, + ConfirmDialogComponent, + CheckComponent, + SafeHtmlPipe, + SelectComponent, + TextComponent, + PasswordComponent, + NumberComponent, + TagsComponent, + PermissionsUserComponent, + PermissionsGroupComponent, + IfOwnerDirective, + ], + providers: [CustomDatePipe, DatePipe, PermissionsGuard], + imports: [ + NgbModule, + HttpClientTestingModule, + RouterTestingModule.withRoutes(routes), + FormsModule, + ReactiveFormsModule, + NgbAlertModule, + NgSelectModule, + ], + }).compileComponents() + fixture = TestBed.createComponent(UsersAndGroupsComponent) + settingsService = TestBed.inject(SettingsService) + settingsService.currentUser = users[0] + userService = TestBed.inject(UserService) + 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) + groupService = TestBed.inject(GroupService) + component = fixture.componentInstance + fixture.detectChanges() + }) + + function completeSetup(excludeService = null) { + if (excludeService !== userService) { + jest.spyOn(userService, 'listAll').mockReturnValue( + of({ + all: users.map((a) => a.id), + count: users.length, + results: (users as PaperlessUser[]).concat([]), + }) + ) + } + if (excludeService !== groupService) { + jest.spyOn(groupService, 'listAll').mockReturnValue( + of({ + all: groups.map((r) => r.id), + count: groups.length, + results: (groups as PaperlessGroup[]).concat([]), + }) + ) + } + + fixture = TestBed.createComponent(UsersAndGroupsComponent) + component = fixture.componentInstance + fixture.detectChanges() + } + + it('should support edit / create user, show error if needed', () => { + completeSetup() + let modal: NgbModalRef + modalService.activeInstances.subscribe((refs) => (modal = refs[0])) + component.editUser(users[0]) + const editDialog = modal.componentInstance as UserEditDialogComponent + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastInfoSpy = jest.spyOn(toastService, 'showInfo') + editDialog.failed.emit() + expect(toastErrorSpy).toBeCalled() + settingsService.currentUser = users[1] // simulate logged in as different user + editDialog.succeeded.emit(users[0]) + expect(toastInfoSpy).toHaveBeenCalledWith( + `Saved user "${users[0].username}".` + ) + component.editUser() + }) + + it('should support delete user, show error if needed', () => { + completeSetup() + let modal: NgbModalRef + modalService.activeInstances.subscribe((refs) => (modal = refs[0])) + component.deleteUser(users[0]) + const deleteDialog = modal.componentInstance as ConfirmDialogComponent + const deleteSpy = jest.spyOn(userService, 'delete') + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastInfoSpy = jest.spyOn(toastService, 'showInfo') + const listAllSpy = jest.spyOn(userService, 'listAll') + deleteSpy.mockReturnValueOnce( + throwError(() => new Error('error deleting user')) + ) + deleteDialog.confirm() + expect(toastErrorSpy).toBeCalled() + deleteSpy.mockReturnValueOnce(of(true)) + deleteDialog.confirm() + expect(listAllSpy).toHaveBeenCalled() + expect(toastInfoSpy).toHaveBeenCalledWith('Deleted user') + }) + + it('should logout current user if password changed, after delay', fakeAsync(() => { + completeSetup() + let modal: NgbModalRef + modalService.activeInstances.subscribe((refs) => (modal = refs[0])) + component.editUser(users[0]) + const editDialog = modal.componentInstance as UserEditDialogComponent + editDialog.passwordIsSet = true + settingsService.currentUser = users[0] // simulate logged in as same user + editDialog.succeeded.emit(users[0]) + fixture.detectChanges() + Object.defineProperty(window, 'location', { + value: { + href: 'http://localhost/', + }, + writable: true, // possibility to override + }) + tick(2600) + expect(window.location.href).toContain('logout') + })) + + it('should support edit / create group, show error if needed', () => { + completeSetup() + let modal: NgbModalRef + modalService.activeInstances.subscribe((refs) => (modal = refs[0])) + component.editGroup(groups[0]) + const editDialog = modal.componentInstance as GroupEditDialogComponent + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastInfoSpy = jest.spyOn(toastService, 'showInfo') + editDialog.failed.emit() + expect(toastErrorSpy).toBeCalled() + editDialog.succeeded.emit(groups[0]) + expect(toastInfoSpy).toHaveBeenCalledWith( + `Saved group "${groups[0].name}".` + ) + component.editGroup() + }) + + it('should support delete group, show error if needed', () => { + completeSetup() + let modal: NgbModalRef + modalService.activeInstances.subscribe((refs) => (modal = refs[0])) + component.deleteGroup(users[0]) + const deleteDialog = modal.componentInstance as ConfirmDialogComponent + const deleteSpy = jest.spyOn(groupService, 'delete') + const toastErrorSpy = jest.spyOn(toastService, 'showError') + const toastInfoSpy = jest.spyOn(toastService, 'showInfo') + const listAllSpy = jest.spyOn(groupService, 'listAll') + deleteSpy.mockReturnValueOnce( + throwError(() => new Error('error deleting group')) + ) + deleteDialog.confirm() + expect(toastErrorSpy).toBeCalled() + deleteSpy.mockReturnValueOnce(of(true)) + deleteDialog.confirm() + expect(listAllSpy).toHaveBeenCalled() + expect(toastInfoSpy).toHaveBeenCalledWith('Deleted group') + }) + + it('should get group name', () => { + completeSetup() + expect(component.getGroupName(1)).toEqual(groups[0].name) + expect(component.getGroupName(11)).toEqual('') + }) + + it('should show errors on load if load users failure', () => { + const toastErrorSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(userService, 'listAll') + .mockImplementation(() => + throwError(() => new Error('failed to load users')) + ) + completeSetup(userService) + fixture.detectChanges() + expect(toastErrorSpy).toBeCalled() + }) + + it('should show errors on load if load groups failure', () => { + const toastErrorSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(groupService, 'listAll') + .mockImplementation(() => + throwError(() => new Error('failed to load groups')) + ) + completeSetup(groupService) + fixture.detectChanges() + expect(toastErrorSpy).toBeCalled() + }) +}) diff --git a/src-ui/src/app/components/admin/users-groups/users-groups.component.ts b/src-ui/src/app/components/admin/users-groups/users-groups.component.ts new file mode 100644 index 000000000..a9ce1d600 --- /dev/null +++ b/src-ui/src/app/components/admin/users-groups/users-groups.component.ts @@ -0,0 +1,189 @@ +import { Component, OnDestroy, OnInit } from '@angular/core' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { Subject, first, takeUntil } from 'rxjs' +import { PaperlessGroup } from 'src/app/data/paperless-group' +import { PaperlessUser } from 'src/app/data/paperless-user' +import { PermissionsService } from 'src/app/services/permissions.service' +import { GroupService } from 'src/app/services/rest/group.service' +import { UserService } from 'src/app/services/rest/user.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 { 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' +import { SettingsService } from 'src/app/services/settings.service' + +@Component({ + selector: 'pngx-users-groups', + templateUrl: './users-groups.component.html', + styleUrls: ['./users-groups.component.scss'], +}) +export class UsersAndGroupsComponent + extends ComponentWithPermissions + implements OnInit, OnDestroy +{ + users: PaperlessUser[] + groups: PaperlessGroup[] + + unsubscribeNotifier: Subject = new Subject() + + constructor( + private usersService: UserService, + private groupsService: GroupService, + private toastService: ToastService, + private modalService: NgbModal, + public permissionsService: PermissionsService, + private settings: SettingsService + ) { + super() + } + + ngOnInit(): void { + this.usersService + .listAll(null, null, { full_perms: true }) + .pipe(first(), takeUntil(this.unsubscribeNotifier)) + .subscribe({ + next: (r) => { + this.users = r.results + }, + error: (e) => { + this.toastService.showError($localize`Error retrieving users`, e) + }, + }) + + this.groupsService + .listAll(null, null, { full_perms: true }) + .pipe(first(), takeUntil(this.unsubscribeNotifier)) + .subscribe({ + next: (r) => { + this.groups = r.results + }, + error: (e) => { + this.toastService.showError($localize`Error retrieving groups`, e) + }, + }) + } + + ngOnDestroy() { + this.unsubscribeNotifier.next(true) + } + + editUser(user: PaperlessUser = null) { + var modal = this.modalService.open(UserEditDialogComponent, { + backdrop: 'static', + size: 'xl', + }) + modal.componentInstance.dialogMode = user + ? EditDialogMode.EDIT + : EditDialogMode.CREATE + modal.componentInstance.object = user + modal.componentInstance.succeeded + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe((newUser: PaperlessUser) => { + if ( + newUser.id === this.settings.currentUser.id && + (modal.componentInstance as UserEditDialogComponent).passwordIsSet + ) { + this.toastService.showInfo( + $localize`Password has been changed, you will be logged out momentarily.` + ) + setTimeout(() => { + window.location.href = `${window.location.origin}/accounts/logout/?next=/accounts/login/` + }, 2500) + } else { + this.toastService.showInfo( + $localize`Saved user "${newUser.username}".` + ) + this.usersService.listAll().subscribe((r) => { + this.users = r.results + }) + } + }) + modal.componentInstance.failed + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe((e) => { + this.toastService.showError($localize`Error saving user.`, e) + }) + } + + deleteUser(user: PaperlessUser) { + let modal = this.modalService.open(ConfirmDialogComponent, { + backdrop: 'static', + }) + modal.componentInstance.title = $localize`Confirm delete user account` + modal.componentInstance.messageBold = $localize`This operation will permanently delete this user account.` + modal.componentInstance.message = $localize`This operation cannot be undone.` + modal.componentInstance.btnClass = 'btn-danger' + modal.componentInstance.btnCaption = $localize`Proceed` + modal.componentInstance.confirmClicked.subscribe(() => { + modal.componentInstance.buttonsEnabled = false + this.usersService.delete(user).subscribe({ + next: () => { + modal.close() + this.toastService.showInfo($localize`Deleted user`) + this.usersService.listAll().subscribe((r) => { + this.users = r.results + }) + }, + error: (e) => { + this.toastService.showError($localize`Error deleting user.`, e) + }, + }) + }) + } + + editGroup(group: PaperlessGroup = null) { + var modal = this.modalService.open(GroupEditDialogComponent, { + backdrop: 'static', + size: 'lg', + }) + modal.componentInstance.dialogMode = group + ? EditDialogMode.EDIT + : EditDialogMode.CREATE + modal.componentInstance.object = group + modal.componentInstance.succeeded + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe((newGroup) => { + this.toastService.showInfo($localize`Saved group "${newGroup.name}".`) + this.groupsService.listAll().subscribe((r) => { + this.groups = r.results + }) + }) + modal.componentInstance.failed + .pipe(takeUntil(this.unsubscribeNotifier)) + .subscribe((e) => { + this.toastService.showError($localize`Error saving group.`, e) + }) + } + + deleteGroup(group: PaperlessGroup) { + let modal = this.modalService.open(ConfirmDialogComponent, { + backdrop: 'static', + }) + modal.componentInstance.title = $localize`Confirm delete user group` + modal.componentInstance.messageBold = $localize`This operation will permanently delete this user group.` + modal.componentInstance.message = $localize`This operation cannot be undone.` + modal.componentInstance.btnClass = 'btn-danger' + modal.componentInstance.btnCaption = $localize`Proceed` + modal.componentInstance.confirmClicked.subscribe(() => { + modal.componentInstance.buttonsEnabled = false + this.groupsService.delete(group).subscribe({ + next: () => { + modal.close() + this.toastService.showInfo($localize`Deleted group`) + this.groupsService.listAll().subscribe((r) => { + this.groups = r.results + }) + }, + error: (e) => { + this.toastService.showError($localize`Error deleting group.`, e) + }, + }) + }) + } + + getGroupName(id: number): string { + return this.groups?.find((g) => g.id === id)?.name ?? '' + } +} diff --git a/src-ui/src/app/components/app-frame/app-frame.component.html b/src-ui/src/app/components/app-frame/app-frame.component.html index 61e716714..cb4223e33 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.html +++ b/src-ui/src/app/components/app-frame/app-frame.component.html @@ -48,6 +48,12 @@ Logout + + + + + + Documentation @@ -80,8 +86,8 @@ -
-
+ -
-
+ - + + + - - -