mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-17 10:13:56 -05:00
Merge pull request #2147 from paperless-ngx/feature-permissions
Feature: multi-user permissions
This commit is contained in:
commit
21eb253c57
3
Pipfile
3
Pipfile
@ -61,6 +61,9 @@ bleach = "*"
|
|||||||
scipy = "==1.8.1"
|
scipy = "==1.8.1"
|
||||||
# Newer versions aren't builting yet (see https://www.piwheels.org/project/cryptography/)
|
# Newer versions aren't builting yet (see https://www.piwheels.org/project/cryptography/)
|
||||||
cryptography = "==38.0.1"
|
cryptography = "==38.0.1"
|
||||||
|
django-guardian = "*"
|
||||||
|
djangorestframework-guardian = "*"
|
||||||
|
|
||||||
# Locked version until https://github.com/django/channels_redis/issues/332
|
# Locked version until https://github.com/django/channels_redis/issues/332
|
||||||
# is resolved
|
# is resolved
|
||||||
channels-redis = "==3.4.1"
|
channels-redis = "==3.4.1"
|
||||||
|
32
Pipfile.lock
generated
32
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "d70848276d3ac35fa361c15ac2d634344cdb08618790502669eee209fc16fa00"
|
"sha256": "99f415c5ce96020dc3fcb137dc15d47cc5431686bdce1ca42e6254a2719060a8"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
@ -313,7 +313,7 @@
|
|||||||
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
|
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
|
||||||
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
|
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==2.1.1"
|
"version": "==2.1.1"
|
||||||
},
|
},
|
||||||
"click": {
|
"click": {
|
||||||
@ -329,7 +329,7 @@
|
|||||||
"sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
|
"sha256:a0713dc7a1de3f06bc0df5a9567ad19ead2d3d5689b434768a6145bff77c0667",
|
||||||
"sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
|
"sha256:f184f0d851d96b6d29297354ed981b7dd71df7ff500d82fa6d11f0856bee8035"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.2' and python_full_version < '4.0.0'",
|
"markers": "python_version < '4' and python_full_version >= '3.6.2'",
|
||||||
"version": "==0.3.0"
|
"version": "==0.3.0"
|
||||||
},
|
},
|
||||||
"click-plugins": {
|
"click-plugins": {
|
||||||
@ -472,6 +472,14 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==22.1"
|
"version": "==22.1"
|
||||||
},
|
},
|
||||||
|
"django-guardian": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:440ca61358427e575323648b25f8384739e54c38b3d655c81d75e0cd0d61b697",
|
||||||
|
"sha256:c58a68ae76922d33e6bdc0e69af1892097838de56e93e78a8361090bcd9f89a0"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.4.0"
|
||||||
|
},
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8",
|
"sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8",
|
||||||
@ -480,6 +488,14 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.14.0"
|
"version": "==3.14.0"
|
||||||
},
|
},
|
||||||
|
"djangorestframework-guardian": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1883756452d9bfcc2a51fb4e039a6837a8f6697c756447aa83af085749b59330",
|
||||||
|
"sha256:3bd3dd6ea58e1bceca5048faf6f8b1a93bb5dcff30ba5eb91b9a0e190a48a0c7"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.3.0"
|
||||||
|
},
|
||||||
"filelock": {
|
"filelock": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de",
|
"sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de",
|
||||||
@ -2205,7 +2221,7 @@
|
|||||||
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
|
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
|
||||||
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
|
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==2.1.1"
|
"version": "==2.1.1"
|
||||||
},
|
},
|
||||||
"click": {
|
"click": {
|
||||||
@ -2401,7 +2417,7 @@
|
|||||||
"sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874",
|
"sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874",
|
||||||
"sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"
|
"sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==3.3.7"
|
"version": "==3.3.7"
|
||||||
},
|
},
|
||||||
"markupsafe": {
|
"markupsafe": {
|
||||||
@ -2455,7 +2471,7 @@
|
|||||||
"sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8",
|
"sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8",
|
||||||
"sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"
|
"sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==1.3.4"
|
"version": "==1.3.4"
|
||||||
},
|
},
|
||||||
"mkdocs": {
|
"mkdocs": {
|
||||||
@ -2800,7 +2816,7 @@
|
|||||||
"sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb",
|
"sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb",
|
||||||
"sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"
|
"sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==0.1"
|
"version": "==0.1"
|
||||||
},
|
},
|
||||||
"regex": {
|
"regex": {
|
||||||
@ -2987,7 +3003,7 @@
|
|||||||
"sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4",
|
"sha256:ce3b1684d6e1a20a3e5ed36795a97dfc6af29bc3970ca8dab93e11ac6094b3c4",
|
||||||
"sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"
|
"sha256:f8b927684efc6f1cc206c9db297a570ab9ad0e51c16fa9e45487d36d1905c058"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_full_version >= '3.6.0'",
|
||||||
"version": "==20.17.1"
|
"version": "==20.17.1"
|
||||||
},
|
},
|
||||||
"watchdog": {
|
"watchdog": {
|
||||||
|
@ -16,6 +16,8 @@ The API provides 7 main endpoints:
|
|||||||
- `/api/tags/`: Full CRUD support.
|
- `/api/tags/`: Full CRUD support.
|
||||||
- `/api/mail_accounts/`: Full CRUD support.
|
- `/api/mail_accounts/`: Full CRUD support.
|
||||||
- `/api/mail_rules/`: Full CRUD support.
|
- `/api/mail_rules/`: Full CRUD support.
|
||||||
|
- `/api/users/`: Full CRUD support.
|
||||||
|
- `/api/groups/`: Full CRUD support.
|
||||||
|
|
||||||
All of these endpoints except for the logging endpoint allow you to
|
All of these endpoints except for the logging endpoint allow you to
|
||||||
fetch, edit and delete individual objects by appending their primary key
|
fetch, edit and delete individual objects by appending their primary key
|
||||||
@ -254,6 +256,7 @@ The endpoint supports the following optional form fields:
|
|||||||
- `document_type`: Similar to correspondent.
|
- `document_type`: Similar to correspondent.
|
||||||
- `tags`: Similar to correspondent. Specify this multiple times to
|
- `tags`: Similar to correspondent. Specify this multiple times to
|
||||||
have multiple tags added to the document.
|
have multiple tags added to the document.
|
||||||
|
- `owner`: An optional user ID to set as the owner.
|
||||||
|
|
||||||
The endpoint will immediately return "OK" if the document consumption
|
The endpoint will immediately return "OK" if the document consumption
|
||||||
process was started successfully. No additional status information about
|
process was started successfully. No additional status information about
|
||||||
|
@ -202,6 +202,39 @@ configured via `PAPERLESS_EMAIL_TASK_CRON` (see [software tweaks](/configuration
|
|||||||
You can also submit a document using the REST API, see [POSTing documents](/api#file-uploads)
|
You can also submit a document using the REST API, see [POSTing documents](/api#file-uploads)
|
||||||
for details.
|
for details.
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
As of version 1.13.0 Paperless-ngx added core support for user / group permissions. Permissions is
|
||||||
|
based around an object 'owner' and 'view' and 'edit' permissions can be granted to other users
|
||||||
|
or groups.
|
||||||
|
|
||||||
|
Permissions uses the built-in user model of the backend framework, Django.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
After migration to version 1.13.0 all existing documents, tags etc. will have no explicit owner
|
||||||
|
set which means they will be visible / editable by all users. Once an object has an owner set,
|
||||||
|
only the owner can explicitly grant / revoke permissions.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
When first migrating to permissions it is recommended to user a 'superuser' account (which
|
||||||
|
would usually have been setup during installation) to ensure you have full permissions.
|
||||||
|
|
||||||
|
Note that superusers have access to all objects.
|
||||||
|
|
||||||
|
Permissions can be set using the new "Permissions" tab when editing documents, or bulk-applied
|
||||||
|
in the UI by selecting documents and choosing the "Permissions" button. Owner can also optionally
|
||||||
|
be set for documents uploaded via the API. Documents consumed via the consumption dir currently
|
||||||
|
do not have an owner set.
|
||||||
|
|
||||||
|
### Users and Groups
|
||||||
|
|
||||||
|
Paperless-ngx versions after 1.13.0 allow creating and editing users and groups via the 'frontend' UI.
|
||||||
|
These can be found under Settings > Users & Groups, assuming the user has access. If a user is designated
|
||||||
|
as a member of a group those permissions will be inherited and this is reflected in the UI. Explicit
|
||||||
|
permissions can be granted to limit access to certain parts of the UI (and corresponding API endpoints).
|
||||||
|
|
||||||
## Best practices {#basic-searching}
|
## Best practices {#basic-searching}
|
||||||
|
|
||||||
Paperless offers a couple tools that help you organize your document
|
Paperless offers a couple tools that help you organize your document
|
||||||
|
68
src-ui/cypress/e2e/auth/auth.cy.ts
Normal file
68
src-ui/cypress/e2e/auth/auth.cy.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
describe('settings', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// also uses global fixtures from cypress/support/e2e.ts
|
||||||
|
|
||||||
|
// mock restricted permissions
|
||||||
|
cy.intercept('http://localhost:8000/api/ui_settings/', {
|
||||||
|
fixture: 'ui_settings/settings_restricted.json',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow user to edit settings', () => {
|
||||||
|
cy.visit('/dashboard')
|
||||||
|
cy.contains('Settings').should('not.exist')
|
||||||
|
cy.visit('/settings').wait(2000)
|
||||||
|
cy.contains("You don't have permissions to do that").should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow user to view documents', () => {
|
||||||
|
cy.visit('/dashboard')
|
||||||
|
cy.contains('Documents').should('not.exist')
|
||||||
|
cy.visit('/documents').wait(2000)
|
||||||
|
cy.contains("You don't have permissions to do that").should('exist')
|
||||||
|
cy.visit('/documents/1').wait(2000)
|
||||||
|
cy.contains("You don't have permissions to do that").should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow user to view correspondents', () => {
|
||||||
|
cy.visit('/dashboard')
|
||||||
|
cy.contains('Correspondents').should('not.exist')
|
||||||
|
cy.visit('/correspondents').wait(2000)
|
||||||
|
cy.contains("You don't have permissions to do that").should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow user to view tags', () => {
|
||||||
|
cy.visit('/dashboard')
|
||||||
|
cy.contains('Tags').should('not.exist')
|
||||||
|
cy.visit('/tags').wait(2000)
|
||||||
|
cy.contains("You don't have permissions to do that").should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow user to view document types', () => {
|
||||||
|
cy.visit('/dashboard')
|
||||||
|
cy.contains('Document Types').should('not.exist')
|
||||||
|
cy.visit('/documenttypes').wait(2000)
|
||||||
|
cy.contains("You don't have permissions to do that").should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow user to view storage paths', () => {
|
||||||
|
cy.visit('/dashboard')
|
||||||
|
cy.contains('Storage Paths').should('not.exist')
|
||||||
|
cy.visit('/storagepaths').wait(2000)
|
||||||
|
cy.contains("You don't have permissions to do that").should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow user to view logs', () => {
|
||||||
|
cy.visit('/dashboard')
|
||||||
|
cy.contains('Logs').should('not.exist')
|
||||||
|
cy.visit('/logs').wait(2000)
|
||||||
|
cy.contains("You don't have permissions to do that").should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not allow user to view tasks', () => {
|
||||||
|
cy.visit('/dashboard')
|
||||||
|
cy.contains('Tasks').should('not.exist')
|
||||||
|
cy.visit('/tasks').wait(2000)
|
||||||
|
cy.contains("You don't have permissions to do that").should('exist')
|
||||||
|
})
|
||||||
|
})
|
@ -6,8 +6,8 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"username": "user2",
|
"username": "user2",
|
||||||
"firstname": "",
|
"first_name": "",
|
||||||
"lastname": ""
|
"last_name": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -17,8 +17,8 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"username": "user1",
|
"username": "user1",
|
||||||
"firstname": "",
|
"first_name": "",
|
||||||
"lastname": ""
|
"last_name": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -28,8 +28,8 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"id": 2,
|
"id": 2,
|
||||||
"username": "user33",
|
"username": "user33",
|
||||||
"firstname": "",
|
"first_name": "",
|
||||||
"lastname": ""
|
"last_name": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -39,8 +39,8 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"username": "admin",
|
"username": "admin",
|
||||||
"firstname": "",
|
"first_name": "",
|
||||||
"lastname": ""
|
"last_name": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -14,11 +14,14 @@
|
|||||||
4
|
4
|
||||||
],
|
],
|
||||||
"created": "2022-03-22T07:24:18Z",
|
"created": "2022-03-22T07:24:18Z",
|
||||||
|
"created_date": "2022-03-22",
|
||||||
"modified": "2022-03-22T07:24:23.264859Z",
|
"modified": "2022-03-22T07:24:23.264859Z",
|
||||||
"added": "2022-03-22T07:24:22.922631Z",
|
"added": "2022-03-22T07:24:22.922631Z",
|
||||||
"archive_serial_number": null,
|
"archive_serial_number": null,
|
||||||
"original_file_name": "2022-03-22 no latin title.pdf",
|
"original_file_name": "2022-03-22 no latin title.pdf",
|
||||||
"archived_file_name": "2022-03-22 no latin title.pdf"
|
"archived_file_name": "2022-03-22 no latin title.pdf",
|
||||||
|
"owner": null,
|
||||||
|
"permissions": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
@ -29,11 +32,14 @@
|
|||||||
"content": "Test document PDF",
|
"content": "Test document PDF",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"created": "2022-03-23T07:24:18Z",
|
"created": "2022-03-23T07:24:18Z",
|
||||||
|
"created_date": "2022-03-23",
|
||||||
"modified": "2022-03-23T07:24:23.264859Z",
|
"modified": "2022-03-23T07:24:23.264859Z",
|
||||||
"added": "2022-03-23T07:24:22.922631Z",
|
"added": "2022-03-23T07:24:22.922631Z",
|
||||||
"archive_serial_number": 12345,
|
"archive_serial_number": 12345,
|
||||||
"original_file_name": "2022-03-23 lorem ipsum dolor sit amet.pdf",
|
"original_file_name": "2022-03-23 lorem ipsum dolor sit amet.pdf",
|
||||||
"archived_file_name": "2022-03-23 llorem ipsum dolor sit amet.pdf"
|
"archived_file_name": "2022-03-23 llorem ipsum dolor sit amet.pdf",
|
||||||
|
"owner": null,
|
||||||
|
"permissions": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
@ -46,11 +52,14 @@
|
|||||||
2
|
2
|
||||||
],
|
],
|
||||||
"created": "2022-03-24T07:24:18Z",
|
"created": "2022-03-24T07:24:18Z",
|
||||||
|
"created_date": "2022-03-24",
|
||||||
"modified": "2022-03-24T07:24:23.264859Z",
|
"modified": "2022-03-24T07:24:23.264859Z",
|
||||||
"added": "2022-03-24T07:24:22.922631Z",
|
"added": "2022-03-24T07:24:22.922631Z",
|
||||||
"archive_serial_number": null,
|
"archive_serial_number": null,
|
||||||
"original_file_name": "2022-03-24 dolor.pdf",
|
"original_file_name": "2022-03-24 dolor.pdf",
|
||||||
"archived_file_name": "2022-03-24 dolor.pdf"
|
"archived_file_name": "2022-03-24 dolor.pdf",
|
||||||
|
"owner": null,
|
||||||
|
"permissions": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 4,
|
||||||
@ -63,11 +72,14 @@
|
|||||||
4, 5
|
4, 5
|
||||||
],
|
],
|
||||||
"created": "2022-06-01T07:24:18Z",
|
"created": "2022-06-01T07:24:18Z",
|
||||||
|
"created_date": "2022-06-01",
|
||||||
"modified": "2022-06-01T07:24:23.264859Z",
|
"modified": "2022-06-01T07:24:23.264859Z",
|
||||||
"added": "2022-06-01T07:24:22.922631Z",
|
"added": "2022-06-01T07:24:22.922631Z",
|
||||||
"archive_serial_number": 12347,
|
"archive_serial_number": 12347,
|
||||||
"original_file_name": "2022-06-01 sit amet.pdf",
|
"original_file_name": "2022-06-01 sit amet.pdf",
|
||||||
"archived_file_name": "2022-06-01 sit amet.pdf"
|
"archived_file_name": "2022-06-01 sit amet.pdf",
|
||||||
|
"owner": null,
|
||||||
|
"permissions": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
119
src-ui/cypress/fixtures/groups/groups.json
Normal file
119
src-ui/cypress/fixtures/groups/groups.json
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
"count": 2,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Another Group",
|
||||||
|
"permissions": [
|
||||||
|
"add_user",
|
||||||
|
"change_user",
|
||||||
|
"delete_user",
|
||||||
|
"view_user",
|
||||||
|
"add_comment",
|
||||||
|
"change_comment",
|
||||||
|
"delete_comment",
|
||||||
|
"view_comment"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "First Group",
|
||||||
|
"permissions": [
|
||||||
|
"add_group",
|
||||||
|
"change_group",
|
||||||
|
"delete_group",
|
||||||
|
"view_group",
|
||||||
|
"add_permission",
|
||||||
|
"change_permission",
|
||||||
|
"delete_permission",
|
||||||
|
"view_permission",
|
||||||
|
"add_token",
|
||||||
|
"change_token",
|
||||||
|
"delete_token",
|
||||||
|
"view_token",
|
||||||
|
"add_tokenproxy",
|
||||||
|
"change_tokenproxy",
|
||||||
|
"delete_tokenproxy",
|
||||||
|
"view_tokenproxy",
|
||||||
|
"add_contenttype",
|
||||||
|
"change_contenttype",
|
||||||
|
"delete_contenttype",
|
||||||
|
"view_contenttype",
|
||||||
|
"add_chordcounter",
|
||||||
|
"change_chordcounter",
|
||||||
|
"delete_chordcounter",
|
||||||
|
"view_chordcounter",
|
||||||
|
"add_groupresult",
|
||||||
|
"change_groupresult",
|
||||||
|
"delete_groupresult",
|
||||||
|
"view_groupresult",
|
||||||
|
"add_taskresult",
|
||||||
|
"change_taskresult",
|
||||||
|
"delete_taskresult",
|
||||||
|
"view_taskresult",
|
||||||
|
"add_failure",
|
||||||
|
"change_failure",
|
||||||
|
"delete_failure",
|
||||||
|
"view_failure",
|
||||||
|
"add_ormq",
|
||||||
|
"change_ormq",
|
||||||
|
"delete_ormq",
|
||||||
|
"view_ormq",
|
||||||
|
"add_schedule",
|
||||||
|
"change_schedule",
|
||||||
|
"delete_schedule",
|
||||||
|
"view_schedule",
|
||||||
|
"add_success",
|
||||||
|
"change_success",
|
||||||
|
"delete_success",
|
||||||
|
"view_success",
|
||||||
|
"add_task",
|
||||||
|
"change_task",
|
||||||
|
"delete_task",
|
||||||
|
"view_task",
|
||||||
|
"add_comment",
|
||||||
|
"change_comment",
|
||||||
|
"delete_comment",
|
||||||
|
"view_comment",
|
||||||
|
"add_correspondent",
|
||||||
|
"change_correspondent",
|
||||||
|
"delete_correspondent",
|
||||||
|
"view_correspondent",
|
||||||
|
"add_document",
|
||||||
|
"change_document",
|
||||||
|
"delete_document",
|
||||||
|
"view_document",
|
||||||
|
"add_documenttype",
|
||||||
|
"change_documenttype",
|
||||||
|
"delete_documenttype",
|
||||||
|
"view_documenttype",
|
||||||
|
"add_frontendsettings",
|
||||||
|
"change_frontendsettings",
|
||||||
|
"delete_frontendsettings",
|
||||||
|
"view_frontendsettings",
|
||||||
|
"add_log",
|
||||||
|
"change_log",
|
||||||
|
"delete_log",
|
||||||
|
"view_log",
|
||||||
|
"add_savedview",
|
||||||
|
"change_savedview",
|
||||||
|
"delete_savedview",
|
||||||
|
"view_savedview",
|
||||||
|
"add_savedviewfilterrule",
|
||||||
|
"change_savedviewfilterrule",
|
||||||
|
"delete_savedviewfilterrule",
|
||||||
|
"view_savedviewfilterrule",
|
||||||
|
"add_taskattributes",
|
||||||
|
"change_taskattributes",
|
||||||
|
"delete_taskattributes",
|
||||||
|
"view_taskattributes",
|
||||||
|
"add_session",
|
||||||
|
"change_session",
|
||||||
|
"delete_session",
|
||||||
|
"view_session"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"user_id": 1,
|
"user_id": 1,
|
||||||
"username": "admin",
|
"username": "admin",
|
||||||
"display_name": "Admin",
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"language": "",
|
"language": "",
|
||||||
"bulk_edit": {
|
"bulk_edit": {
|
||||||
@ -30,5 +29,131 @@
|
|||||||
"consumer_failed": true,
|
"consumer_failed": true,
|
||||||
"consumer_suppress_on_dashboard": true
|
"consumer_suppress_on_dashboard": true
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"permissions": [
|
||||||
|
"add_logentry",
|
||||||
|
"change_logentry",
|
||||||
|
"delete_logentry",
|
||||||
|
"view_logentry",
|
||||||
|
"add_group",
|
||||||
|
"change_group",
|
||||||
|
"delete_group",
|
||||||
|
"view_group",
|
||||||
|
"add_permission",
|
||||||
|
"change_permission",
|
||||||
|
"delete_permission",
|
||||||
|
"view_permission",
|
||||||
|
"add_user",
|
||||||
|
"change_user",
|
||||||
|
"delete_user",
|
||||||
|
"view_user",
|
||||||
|
"add_token",
|
||||||
|
"change_token",
|
||||||
|
"delete_token",
|
||||||
|
"view_token",
|
||||||
|
"add_tokenproxy",
|
||||||
|
"change_tokenproxy",
|
||||||
|
"delete_tokenproxy",
|
||||||
|
"view_tokenproxy",
|
||||||
|
"add_contenttype",
|
||||||
|
"change_contenttype",
|
||||||
|
"delete_contenttype",
|
||||||
|
"view_contenttype",
|
||||||
|
"add_chordcounter",
|
||||||
|
"change_chordcounter",
|
||||||
|
"delete_chordcounter",
|
||||||
|
"view_chordcounter",
|
||||||
|
"add_groupresult",
|
||||||
|
"change_groupresult",
|
||||||
|
"delete_groupresult",
|
||||||
|
"view_groupresult",
|
||||||
|
"add_taskresult",
|
||||||
|
"change_taskresult",
|
||||||
|
"delete_taskresult",
|
||||||
|
"view_taskresult",
|
||||||
|
"add_failure",
|
||||||
|
"change_failure",
|
||||||
|
"delete_failure",
|
||||||
|
"view_failure",
|
||||||
|
"add_ormq",
|
||||||
|
"change_ormq",
|
||||||
|
"delete_ormq",
|
||||||
|
"view_ormq",
|
||||||
|
"add_schedule",
|
||||||
|
"change_schedule",
|
||||||
|
"delete_schedule",
|
||||||
|
"view_schedule",
|
||||||
|
"add_success",
|
||||||
|
"change_success",
|
||||||
|
"delete_success",
|
||||||
|
"view_success",
|
||||||
|
"add_task",
|
||||||
|
"change_task",
|
||||||
|
"delete_task",
|
||||||
|
"view_task",
|
||||||
|
"add_comment",
|
||||||
|
"change_comment",
|
||||||
|
"delete_comment",
|
||||||
|
"view_comment",
|
||||||
|
"add_correspondent",
|
||||||
|
"change_correspondent",
|
||||||
|
"delete_correspondent",
|
||||||
|
"view_correspondent",
|
||||||
|
"add_document",
|
||||||
|
"change_document",
|
||||||
|
"delete_document",
|
||||||
|
"view_document",
|
||||||
|
"add_documenttype",
|
||||||
|
"change_documenttype",
|
||||||
|
"delete_documenttype",
|
||||||
|
"view_documenttype",
|
||||||
|
"add_frontendsettings",
|
||||||
|
"change_frontendsettings",
|
||||||
|
"delete_frontendsettings",
|
||||||
|
"view_frontendsettings",
|
||||||
|
"add_log",
|
||||||
|
"change_log",
|
||||||
|
"delete_log",
|
||||||
|
"view_log",
|
||||||
|
"add_paperlesstask",
|
||||||
|
"change_paperlesstask",
|
||||||
|
"delete_paperlesstask",
|
||||||
|
"view_paperlesstask",
|
||||||
|
"add_savedview",
|
||||||
|
"change_savedview",
|
||||||
|
"delete_savedview",
|
||||||
|
"view_savedview",
|
||||||
|
"add_savedviewfilterrule",
|
||||||
|
"change_savedviewfilterrule",
|
||||||
|
"delete_savedviewfilterrule",
|
||||||
|
"view_savedviewfilterrule",
|
||||||
|
"add_storagepath",
|
||||||
|
"change_storagepath",
|
||||||
|
"delete_storagepath",
|
||||||
|
"view_storagepath",
|
||||||
|
"add_tag",
|
||||||
|
"change_tag",
|
||||||
|
"delete_tag",
|
||||||
|
"view_tag",
|
||||||
|
"add_taskattributes",
|
||||||
|
"change_taskattributes",
|
||||||
|
"delete_taskattributes",
|
||||||
|
"view_taskattributes",
|
||||||
|
"add_uisettings",
|
||||||
|
"change_uisettings",
|
||||||
|
"delete_uisettings",
|
||||||
|
"view_uisettings",
|
||||||
|
"add_mailaccount",
|
||||||
|
"change_mailaccount",
|
||||||
|
"delete_mailaccount",
|
||||||
|
"view_mailaccount",
|
||||||
|
"add_mailrule",
|
||||||
|
"change_mailrule",
|
||||||
|
"delete_mailrule",
|
||||||
|
"view_mailrule",
|
||||||
|
"add_session",
|
||||||
|
"change_session",
|
||||||
|
"delete_session",
|
||||||
|
"view_session"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
84
src-ui/cypress/fixtures/ui_settings/settings_restricted.json
Normal file
84
src-ui/cypress/fixtures/ui_settings/settings_restricted.json
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"user_id": 1,
|
||||||
|
"username": "admin",
|
||||||
|
"settings": {
|
||||||
|
"language": "",
|
||||||
|
"bulk_edit": {
|
||||||
|
"confirmation_dialogs": true,
|
||||||
|
"apply_on_close": false
|
||||||
|
},
|
||||||
|
"documentListSize": 50,
|
||||||
|
"dark_mode": {
|
||||||
|
"use_system": true,
|
||||||
|
"enabled": "false",
|
||||||
|
"thumb_inverted": "true"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"color": "#b198e5"
|
||||||
|
},
|
||||||
|
"document_details": {
|
||||||
|
"native_pdf_viewer": false
|
||||||
|
},
|
||||||
|
"date_display": {
|
||||||
|
"date_locale": "",
|
||||||
|
"date_format": "mediumDate"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"consumer_new_documents": true,
|
||||||
|
"consumer_success": true,
|
||||||
|
"consumer_failed": true,
|
||||||
|
"consumer_suppress_on_dashboard": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"add_token",
|
||||||
|
"change_token",
|
||||||
|
"delete_token",
|
||||||
|
"view_token",
|
||||||
|
"add_tokenproxy",
|
||||||
|
"change_tokenproxy",
|
||||||
|
"delete_tokenproxy",
|
||||||
|
"view_tokenproxy",
|
||||||
|
"add_contenttype",
|
||||||
|
"change_contenttype",
|
||||||
|
"delete_contenttype",
|
||||||
|
"view_contenttype",
|
||||||
|
"add_chordcounter",
|
||||||
|
"change_chordcounter",
|
||||||
|
"delete_chordcounter",
|
||||||
|
"view_chordcounter",
|
||||||
|
"add_groupresult",
|
||||||
|
"change_groupresult",
|
||||||
|
"delete_groupresult",
|
||||||
|
"view_groupresult",
|
||||||
|
"add_failure",
|
||||||
|
"change_failure",
|
||||||
|
"delete_failure",
|
||||||
|
"view_failure",
|
||||||
|
"add_ormq",
|
||||||
|
"change_ormq",
|
||||||
|
"delete_ormq",
|
||||||
|
"view_ormq",
|
||||||
|
"add_schedule",
|
||||||
|
"change_schedule",
|
||||||
|
"delete_schedule",
|
||||||
|
"view_schedule",
|
||||||
|
"add_success",
|
||||||
|
"change_success",
|
||||||
|
"delete_success",
|
||||||
|
"view_success",
|
||||||
|
"add_task",
|
||||||
|
"change_task",
|
||||||
|
"delete_task",
|
||||||
|
"view_task",
|
||||||
|
"add_comment",
|
||||||
|
"add_frontendsettings",
|
||||||
|
"change_frontendsettings",
|
||||||
|
"delete_frontendsettings",
|
||||||
|
"view_frontendsettings",
|
||||||
|
"add_session",
|
||||||
|
"change_session",
|
||||||
|
"delete_session",
|
||||||
|
"view_session"
|
||||||
|
]
|
||||||
|
}
|
459
src-ui/cypress/fixtures/users/users.json
Normal file
459
src-ui/cypress/fixtures/users/users.json
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
{
|
||||||
|
"count": 4,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"username": "admin",
|
||||||
|
"password": "**********",
|
||||||
|
"first_name": "",
|
||||||
|
"last_name": "",
|
||||||
|
"date_joined": "2022-02-14T23:11:09.103293Z",
|
||||||
|
"is_staff": true,
|
||||||
|
"is_active": true,
|
||||||
|
"is_superuser": true,
|
||||||
|
"groups": [],
|
||||||
|
"user_permissions": [],
|
||||||
|
"inherited_permissions": [
|
||||||
|
"auth.delete_permission",
|
||||||
|
"paperless_mail.change_mailrule",
|
||||||
|
"django_celery_results.add_taskresult",
|
||||||
|
"documents.view_taskattributes",
|
||||||
|
"documents.view_paperlesstask",
|
||||||
|
"django_q.add_success",
|
||||||
|
"documents.view_uisettings",
|
||||||
|
"auth.change_user",
|
||||||
|
"admin.delete_logentry",
|
||||||
|
"django_celery_results.change_taskresult",
|
||||||
|
"django_q.change_schedule",
|
||||||
|
"django_celery_results.delete_taskresult",
|
||||||
|
"paperless_mail.add_mailaccount",
|
||||||
|
"auth.change_group",
|
||||||
|
"documents.add_comment",
|
||||||
|
"paperless_mail.delete_mailaccount",
|
||||||
|
"authtoken.delete_tokenproxy",
|
||||||
|
"guardian.delete_groupobjectpermission",
|
||||||
|
"contenttypes.delete_contenttype",
|
||||||
|
"documents.change_correspondent",
|
||||||
|
"authtoken.delete_token",
|
||||||
|
"documents.delete_documenttype",
|
||||||
|
"django_q.change_ormq",
|
||||||
|
"documents.change_savedviewfilterrule",
|
||||||
|
"auth.delete_group",
|
||||||
|
"documents.add_documenttype",
|
||||||
|
"django_q.change_success",
|
||||||
|
"documents.delete_tag",
|
||||||
|
"documents.change_comment",
|
||||||
|
"django_q.delete_task",
|
||||||
|
"documents.add_savedviewfilterrule",
|
||||||
|
"django_q.view_task",
|
||||||
|
"paperless_mail.add_mailrule",
|
||||||
|
"paperless_mail.view_mailaccount",
|
||||||
|
"documents.add_frontendsettings",
|
||||||
|
"sessions.change_session",
|
||||||
|
"documents.view_savedview",
|
||||||
|
"authtoken.add_tokenproxy",
|
||||||
|
"documents.change_tag",
|
||||||
|
"documents.view_document",
|
||||||
|
"documents.add_savedview",
|
||||||
|
"auth.delete_user",
|
||||||
|
"documents.view_log",
|
||||||
|
"documents.view_comment",
|
||||||
|
"guardian.change_groupobjectpermission",
|
||||||
|
"sessions.delete_session",
|
||||||
|
"django_q.change_failure",
|
||||||
|
"guardian.change_userobjectpermission",
|
||||||
|
"documents.change_storagepath",
|
||||||
|
"documents.delete_document",
|
||||||
|
"documents.delete_taskattributes",
|
||||||
|
"django_celery_results.change_groupresult",
|
||||||
|
"django_q.add_ormq",
|
||||||
|
"guardian.view_groupobjectpermission",
|
||||||
|
"admin.change_logentry",
|
||||||
|
"django_q.delete_schedule",
|
||||||
|
"documents.delete_paperlesstask",
|
||||||
|
"django_q.view_ormq",
|
||||||
|
"documents.change_paperlesstask",
|
||||||
|
"guardian.delete_userobjectpermission",
|
||||||
|
"auth.view_permission",
|
||||||
|
"auth.view_user",
|
||||||
|
"django_q.add_schedule",
|
||||||
|
"authtoken.change_token",
|
||||||
|
"guardian.add_groupobjectpermission",
|
||||||
|
"documents.view_documenttype",
|
||||||
|
"documents.change_log",
|
||||||
|
"paperless_mail.delete_mailrule",
|
||||||
|
"auth.view_group",
|
||||||
|
"authtoken.view_token",
|
||||||
|
"admin.view_logentry",
|
||||||
|
"django_celery_results.view_chordcounter",
|
||||||
|
"django_celery_results.view_groupresult",
|
||||||
|
"documents.view_storagepath",
|
||||||
|
"documents.add_storagepath",
|
||||||
|
"django_celery_results.add_groupresult",
|
||||||
|
"documents.view_tag",
|
||||||
|
"guardian.view_userobjectpermission",
|
||||||
|
"documents.delete_correspondent",
|
||||||
|
"documents.add_tag",
|
||||||
|
"documents.delete_savedviewfilterrule",
|
||||||
|
"documents.add_correspondent",
|
||||||
|
"authtoken.view_tokenproxy",
|
||||||
|
"documents.delete_frontendsettings",
|
||||||
|
"django_celery_results.delete_chordcounter",
|
||||||
|
"django_q.change_task",
|
||||||
|
"documents.add_taskattributes",
|
||||||
|
"documents.delete_storagepath",
|
||||||
|
"sessions.add_session",
|
||||||
|
"documents.add_uisettings",
|
||||||
|
"documents.change_taskattributes",
|
||||||
|
"documents.delete_uisettings",
|
||||||
|
"django_q.delete_ormq",
|
||||||
|
"auth.change_permission",
|
||||||
|
"documents.view_savedviewfilterrule",
|
||||||
|
"documents.change_frontendsettings",
|
||||||
|
"documents.change_documenttype",
|
||||||
|
"documents.view_correspondent",
|
||||||
|
"auth.add_user",
|
||||||
|
"paperless_mail.change_mailaccount",
|
||||||
|
"documents.add_paperlesstask",
|
||||||
|
"django_q.view_success",
|
||||||
|
"django_celery_results.delete_groupresult",
|
||||||
|
"documents.delete_savedview",
|
||||||
|
"authtoken.change_tokenproxy",
|
||||||
|
"documents.view_frontendsettings",
|
||||||
|
"authtoken.add_token",
|
||||||
|
"django_celery_results.add_chordcounter",
|
||||||
|
"contenttypes.change_contenttype",
|
||||||
|
"admin.add_logentry",
|
||||||
|
"django_q.delete_failure",
|
||||||
|
"documents.change_uisettings",
|
||||||
|
"django_q.view_failure",
|
||||||
|
"documents.add_log",
|
||||||
|
"documents.change_savedview",
|
||||||
|
"paperless_mail.view_mailrule",
|
||||||
|
"django_q.view_schedule",
|
||||||
|
"documents.change_document",
|
||||||
|
"django_celery_results.change_chordcounter",
|
||||||
|
"documents.add_document",
|
||||||
|
"django_celery_results.view_taskresult",
|
||||||
|
"contenttypes.add_contenttype",
|
||||||
|
"django_q.delete_success",
|
||||||
|
"documents.delete_comment",
|
||||||
|
"django_q.add_failure",
|
||||||
|
"guardian.add_userobjectpermission",
|
||||||
|
"sessions.view_session",
|
||||||
|
"contenttypes.view_contenttype",
|
||||||
|
"auth.add_permission",
|
||||||
|
"documents.delete_log",
|
||||||
|
"django_q.add_task",
|
||||||
|
"auth.add_group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"username": "test",
|
||||||
|
"password": "**********",
|
||||||
|
"first_name": "",
|
||||||
|
"last_name": "",
|
||||||
|
"date_joined": "2022-11-23T08:30:54Z",
|
||||||
|
"is_staff": true,
|
||||||
|
"is_active": true,
|
||||||
|
"is_superuser": false,
|
||||||
|
"groups": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"user_permissions": [
|
||||||
|
"add_group",
|
||||||
|
"change_group",
|
||||||
|
"delete_group",
|
||||||
|
"view_group",
|
||||||
|
"add_permission",
|
||||||
|
"change_permission",
|
||||||
|
"delete_permission",
|
||||||
|
"view_permission",
|
||||||
|
"add_token",
|
||||||
|
"change_token",
|
||||||
|
"delete_token",
|
||||||
|
"view_token",
|
||||||
|
"add_tokenproxy",
|
||||||
|
"change_tokenproxy",
|
||||||
|
"delete_tokenproxy",
|
||||||
|
"view_tokenproxy",
|
||||||
|
"add_contenttype",
|
||||||
|
"change_contenttype",
|
||||||
|
"delete_contenttype",
|
||||||
|
"view_contenttype",
|
||||||
|
"add_chordcounter",
|
||||||
|
"change_chordcounter",
|
||||||
|
"delete_chordcounter",
|
||||||
|
"view_chordcounter",
|
||||||
|
"add_groupresult",
|
||||||
|
"change_groupresult",
|
||||||
|
"delete_groupresult",
|
||||||
|
"view_groupresult",
|
||||||
|
"add_taskresult",
|
||||||
|
"change_taskresult",
|
||||||
|
"delete_taskresult",
|
||||||
|
"view_taskresult",
|
||||||
|
"add_failure",
|
||||||
|
"change_failure",
|
||||||
|
"delete_failure",
|
||||||
|
"view_failure",
|
||||||
|
"add_ormq",
|
||||||
|
"change_ormq",
|
||||||
|
"delete_ormq",
|
||||||
|
"view_ormq",
|
||||||
|
"add_schedule",
|
||||||
|
"change_schedule",
|
||||||
|
"delete_schedule",
|
||||||
|
"view_schedule",
|
||||||
|
"add_success",
|
||||||
|
"change_success",
|
||||||
|
"delete_success",
|
||||||
|
"view_success",
|
||||||
|
"add_task",
|
||||||
|
"change_task",
|
||||||
|
"delete_task",
|
||||||
|
"view_task",
|
||||||
|
"add_comment",
|
||||||
|
"change_comment",
|
||||||
|
"delete_comment",
|
||||||
|
"view_comment",
|
||||||
|
"add_frontendsettings",
|
||||||
|
"change_frontendsettings",
|
||||||
|
"delete_frontendsettings",
|
||||||
|
"view_frontendsettings",
|
||||||
|
"add_log",
|
||||||
|
"change_log",
|
||||||
|
"delete_log",
|
||||||
|
"view_log",
|
||||||
|
"add_savedviewfilterrule",
|
||||||
|
"change_savedviewfilterrule",
|
||||||
|
"delete_savedviewfilterrule",
|
||||||
|
"view_savedviewfilterrule",
|
||||||
|
"add_taskattributes",
|
||||||
|
"change_taskattributes",
|
||||||
|
"delete_taskattributes",
|
||||||
|
"view_taskattributes",
|
||||||
|
"add_session",
|
||||||
|
"change_session",
|
||||||
|
"delete_session",
|
||||||
|
"view_session"
|
||||||
|
],
|
||||||
|
"inherited_permissions": [
|
||||||
|
"auth.delete_permission",
|
||||||
|
"django_celery_results.add_taskresult",
|
||||||
|
"documents.view_taskattributes",
|
||||||
|
"django_q.add_ormq",
|
||||||
|
"django_q.add_success",
|
||||||
|
"django_q.delete_schedule",
|
||||||
|
"django_q.view_ormq",
|
||||||
|
"auth.view_permission",
|
||||||
|
"django_q.add_schedule",
|
||||||
|
"django_celery_results.change_taskresult",
|
||||||
|
"django_q.change_schedule",
|
||||||
|
"django_celery_results.delete_taskresult",
|
||||||
|
"authtoken.change_token",
|
||||||
|
"auth.change_group",
|
||||||
|
"documents.add_comment",
|
||||||
|
"authtoken.delete_tokenproxy",
|
||||||
|
"documents.view_documenttype",
|
||||||
|
"contenttypes.delete_contenttype",
|
||||||
|
"documents.change_correspondent",
|
||||||
|
"authtoken.delete_token",
|
||||||
|
"documents.change_log",
|
||||||
|
"auth.view_group",
|
||||||
|
"authtoken.view_token",
|
||||||
|
"django_celery_results.view_chordcounter",
|
||||||
|
"django_celery_results.view_groupresult",
|
||||||
|
"documents.delete_documenttype",
|
||||||
|
"django_q.change_ormq",
|
||||||
|
"documents.change_savedviewfilterrule",
|
||||||
|
"django_celery_results.add_groupresult",
|
||||||
|
"auth.delete_group",
|
||||||
|
"documents.add_documenttype",
|
||||||
|
"django_q.change_success",
|
||||||
|
"auth.add_permission",
|
||||||
|
"documents.delete_correspondent",
|
||||||
|
"documents.delete_savedviewfilterrule",
|
||||||
|
"documents.add_correspondent",
|
||||||
|
"authtoken.view_tokenproxy",
|
||||||
|
"documents.delete_frontendsettings",
|
||||||
|
"django_celery_results.delete_chordcounter",
|
||||||
|
"documents.add_taskattributes",
|
||||||
|
"django_q.change_task",
|
||||||
|
"sessions.add_session",
|
||||||
|
"documents.change_taskattributes",
|
||||||
|
"documents.change_comment",
|
||||||
|
"django_q.delete_task",
|
||||||
|
"django_q.delete_ormq",
|
||||||
|
"auth.change_permission",
|
||||||
|
"documents.add_savedviewfilterrule",
|
||||||
|
"django_q.view_task",
|
||||||
|
"documents.view_savedviewfilterrule",
|
||||||
|
"documents.change_frontendsettings",
|
||||||
|
"documents.change_documenttype",
|
||||||
|
"documents.view_correspondent",
|
||||||
|
"django_q.view_success",
|
||||||
|
"documents.add_frontendsettings",
|
||||||
|
"django_celery_results.delete_groupresult",
|
||||||
|
"documents.delete_savedview",
|
||||||
|
"authtoken.change_tokenproxy",
|
||||||
|
"documents.view_frontendsettings",
|
||||||
|
"authtoken.add_token",
|
||||||
|
"sessions.change_session",
|
||||||
|
"django_celery_results.add_chordcounter",
|
||||||
|
"documents.view_savedview",
|
||||||
|
"contenttypes.change_contenttype",
|
||||||
|
"django_q.delete_failure",
|
||||||
|
"authtoken.add_tokenproxy",
|
||||||
|
"documents.view_document",
|
||||||
|
"documents.add_savedview",
|
||||||
|
"django_q.view_failure",
|
||||||
|
"documents.view_comment",
|
||||||
|
"documents.view_log",
|
||||||
|
"documents.add_log",
|
||||||
|
"documents.change_savedview",
|
||||||
|
"django_q.view_schedule",
|
||||||
|
"documents.change_document",
|
||||||
|
"django_celery_results.change_chordcounter",
|
||||||
|
"documents.add_document",
|
||||||
|
"sessions.delete_session",
|
||||||
|
"django_q.change_failure",
|
||||||
|
"django_celery_results.view_taskresult",
|
||||||
|
"contenttypes.add_contenttype",
|
||||||
|
"django_q.delete_success",
|
||||||
|
"documents.delete_comment",
|
||||||
|
"django_q.add_failure",
|
||||||
|
"sessions.view_session",
|
||||||
|
"contenttypes.view_contenttype",
|
||||||
|
"documents.delete_taskattributes",
|
||||||
|
"documents.delete_document",
|
||||||
|
"documents.delete_log",
|
||||||
|
"django_q.add_task",
|
||||||
|
"django_celery_results.change_groupresult",
|
||||||
|
"auth.add_group"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"username": "testuser",
|
||||||
|
"password": "**********",
|
||||||
|
"first_name": "",
|
||||||
|
"last_name": "",
|
||||||
|
"date_joined": "2022-11-16T04:14:20.484914Z",
|
||||||
|
"is_staff": false,
|
||||||
|
"is_active": true,
|
||||||
|
"is_superuser": false,
|
||||||
|
"groups": [
|
||||||
|
1,
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"user_permissions": [
|
||||||
|
"add_logentry",
|
||||||
|
"change_logentry",
|
||||||
|
"delete_logentry",
|
||||||
|
"view_logentry"
|
||||||
|
],
|
||||||
|
"inherited_permissions": [
|
||||||
|
"auth.delete_permission",
|
||||||
|
"django_celery_results.add_taskresult",
|
||||||
|
"documents.view_taskattributes",
|
||||||
|
"django_q.add_ormq",
|
||||||
|
"django_q.add_success",
|
||||||
|
"django_q.delete_schedule",
|
||||||
|
"django_q.view_ormq",
|
||||||
|
"auth.change_user",
|
||||||
|
"auth.view_permission",
|
||||||
|
"auth.view_user",
|
||||||
|
"django_q.add_schedule",
|
||||||
|
"django_celery_results.change_taskresult",
|
||||||
|
"django_q.change_schedule",
|
||||||
|
"django_celery_results.delete_taskresult",
|
||||||
|
"authtoken.change_token",
|
||||||
|
"auth.change_group",
|
||||||
|
"documents.add_comment",
|
||||||
|
"authtoken.delete_tokenproxy",
|
||||||
|
"documents.view_documenttype",
|
||||||
|
"contenttypes.delete_contenttype",
|
||||||
|
"documents.change_correspondent",
|
||||||
|
"authtoken.delete_token",
|
||||||
|
"documents.change_log",
|
||||||
|
"auth.view_group",
|
||||||
|
"authtoken.view_token",
|
||||||
|
"django_celery_results.view_chordcounter",
|
||||||
|
"django_celery_results.view_groupresult",
|
||||||
|
"documents.delete_documenttype",
|
||||||
|
"django_q.change_ormq",
|
||||||
|
"documents.change_savedviewfilterrule",
|
||||||
|
"django_celery_results.add_groupresult",
|
||||||
|
"auth.delete_group",
|
||||||
|
"documents.add_documenttype",
|
||||||
|
"django_q.change_success",
|
||||||
|
"auth.add_permission",
|
||||||
|
"documents.delete_correspondent",
|
||||||
|
"documents.delete_savedviewfilterrule",
|
||||||
|
"documents.add_correspondent",
|
||||||
|
"authtoken.view_tokenproxy",
|
||||||
|
"documents.delete_frontendsettings",
|
||||||
|
"django_celery_results.delete_chordcounter",
|
||||||
|
"documents.add_taskattributes",
|
||||||
|
"django_q.change_task",
|
||||||
|
"sessions.add_session",
|
||||||
|
"documents.change_taskattributes",
|
||||||
|
"documents.change_comment",
|
||||||
|
"django_q.delete_task",
|
||||||
|
"django_q.delete_ormq",
|
||||||
|
"auth.change_permission",
|
||||||
|
"documents.add_savedviewfilterrule",
|
||||||
|
"django_q.view_task",
|
||||||
|
"documents.view_savedviewfilterrule",
|
||||||
|
"documents.change_frontendsettings",
|
||||||
|
"documents.change_documenttype",
|
||||||
|
"documents.view_correspondent",
|
||||||
|
"auth.add_user",
|
||||||
|
"django_q.view_success",
|
||||||
|
"documents.add_frontendsettings",
|
||||||
|
"django_celery_results.delete_groupresult",
|
||||||
|
"documents.delete_savedview",
|
||||||
|
"authtoken.change_tokenproxy",
|
||||||
|
"documents.view_frontendsettings",
|
||||||
|
"authtoken.add_token",
|
||||||
|
"sessions.change_session",
|
||||||
|
"django_celery_results.add_chordcounter",
|
||||||
|
"documents.view_savedview",
|
||||||
|
"contenttypes.change_contenttype",
|
||||||
|
"django_q.delete_failure",
|
||||||
|
"authtoken.add_tokenproxy",
|
||||||
|
"documents.view_document",
|
||||||
|
"documents.add_savedview",
|
||||||
|
"django_q.view_failure",
|
||||||
|
"documents.view_comment",
|
||||||
|
"documents.view_log",
|
||||||
|
"auth.delete_user",
|
||||||
|
"documents.add_log",
|
||||||
|
"documents.change_savedview",
|
||||||
|
"django_q.view_schedule",
|
||||||
|
"documents.change_document",
|
||||||
|
"django_celery_results.change_chordcounter",
|
||||||
|
"documents.add_document",
|
||||||
|
"sessions.delete_session",
|
||||||
|
"django_q.change_failure",
|
||||||
|
"django_celery_results.view_taskresult",
|
||||||
|
"contenttypes.add_contenttype",
|
||||||
|
"django_q.delete_success",
|
||||||
|
"documents.delete_comment",
|
||||||
|
"django_q.add_failure",
|
||||||
|
"sessions.view_session",
|
||||||
|
"contenttypes.view_contenttype",
|
||||||
|
"documents.delete_taskattributes",
|
||||||
|
"documents.delete_document",
|
||||||
|
"documents.delete_log",
|
||||||
|
"django_q.add_task",
|
||||||
|
"django_celery_results.change_groupresult",
|
||||||
|
"auth.add_group"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -5,6 +5,14 @@ beforeEach(() => {
|
|||||||
fixture: 'ui_settings/settings.json',
|
fixture: 'ui_settings/settings.json',
|
||||||
}).as('ui-settings')
|
}).as('ui-settings')
|
||||||
|
|
||||||
|
cy.intercept('http://localhost:8000/api/users/*', {
|
||||||
|
fixture: 'users/users.json',
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.intercept('http://localhost:8000/api/groups/*', {
|
||||||
|
fixture: 'groups/groups.json',
|
||||||
|
})
|
||||||
|
|
||||||
cy.intercept('http://localhost:8000/api/remote_version/', {
|
cy.intercept('http://localhost:8000/api/remote_version/', {
|
||||||
fixture: 'remote_version/remote_version.json',
|
fixture: 'remote_version/remote_version.json',
|
||||||
})
|
})
|
||||||
|
1191
src-ui/messages.xlf
1191
src-ui/messages.xlf
File diff suppressed because it is too large
Load Diff
@ -14,8 +14,13 @@ import { DocumentAsnComponent } from './components/document-asn/document-asn.com
|
|||||||
import { DirtyFormGuard } from './guards/dirty-form.guard'
|
import { DirtyFormGuard } from './guards/dirty-form.guard'
|
||||||
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
|
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
|
||||||
import { TasksComponent } from './components/manage/tasks/tasks.component'
|
import { TasksComponent } from './components/manage/tasks/tasks.component'
|
||||||
|
import { PermissionsGuard } from './guards/permissions.guard'
|
||||||
import { DirtyDocGuard } from './guards/dirty-doc.guard'
|
import { DirtyDocGuard } from './guards/dirty-doc.guard'
|
||||||
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
|
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
|
||||||
|
import {
|
||||||
|
PermissionAction,
|
||||||
|
PermissionType,
|
||||||
|
} from './services/permissions.service'
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
|
||||||
@ -29,23 +34,137 @@ const routes: Routes = [
|
|||||||
path: 'documents',
|
path: 'documents',
|
||||||
component: DocumentListComponent,
|
component: DocumentListComponent,
|
||||||
canDeactivate: [DirtySavedViewGuard],
|
canDeactivate: [DirtySavedViewGuard],
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.Document,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'view/:id',
|
path: 'view/:id',
|
||||||
component: DocumentListComponent,
|
component: DocumentListComponent,
|
||||||
canDeactivate: [DirtySavedViewGuard],
|
canDeactivate: [DirtySavedViewGuard],
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.SavedView,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'documents/:id',
|
||||||
|
component: DocumentDetailComponent,
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.Document,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'asn/:id',
|
||||||
|
component: DocumentAsnComponent,
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.Document,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tags',
|
||||||
|
component: TagListComponent,
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.Tag,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'documenttypes',
|
||||||
|
component: DocumentTypeListComponent,
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.DocumentType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'correspondents',
|
||||||
|
component: CorrespondentListComponent,
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.Correspondent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'storagepaths',
|
||||||
|
component: StoragePathListComponent,
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.StoragePath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'logs',
|
||||||
|
component: LogsComponent,
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.Admin,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{ path: 'documents/:id', component: DocumentDetailComponent },
|
|
||||||
{ path: 'asn/:id', component: DocumentAsnComponent },
|
|
||||||
{ path: 'tags', component: TagListComponent },
|
|
||||||
{ path: 'documenttypes', component: DocumentTypeListComponent },
|
|
||||||
{ path: 'correspondents', component: CorrespondentListComponent },
|
|
||||||
{ path: 'storagepaths', component: StoragePathListComponent },
|
|
||||||
{ path: 'logs', component: LogsComponent },
|
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
component: SettingsComponent,
|
component: SettingsComponent,
|
||||||
canDeactivate: [DirtyFormGuard],
|
canDeactivate: [DirtyFormGuard],
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.UISettings,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tasks',
|
||||||
|
component: TasksComponent,
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.PaperlessTask,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'settings/:section',
|
||||||
|
component: SettingsComponent,
|
||||||
|
canDeactivate: [DirtyFormGuard],
|
||||||
|
canActivate: [PermissionsGuard],
|
||||||
|
data: {
|
||||||
|
requiredPermission: {
|
||||||
|
action: PermissionAction.View,
|
||||||
|
type: PermissionType.UISettings,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'settings/:section',
|
path: 'settings/:section',
|
||||||
|
@ -9,6 +9,11 @@ import { NgxFileDropEntry } from 'ngx-file-drop'
|
|||||||
import { UploadDocumentsService } from './services/upload-documents.service'
|
import { UploadDocumentsService } from './services/upload-documents.service'
|
||||||
import { TasksService } from './services/tasks.service'
|
import { TasksService } from './services/tasks.service'
|
||||||
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
||||||
|
import {
|
||||||
|
PermissionAction,
|
||||||
|
PermissionsService,
|
||||||
|
PermissionType,
|
||||||
|
} from './services/permissions.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -32,7 +37,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private uploadDocumentsService: UploadDocumentsService,
|
private uploadDocumentsService: UploadDocumentsService,
|
||||||
private tasksService: TasksService,
|
private tasksService: TasksService,
|
||||||
public tourService: TourService,
|
public tourService: TourService,
|
||||||
private renderer: Renderer2
|
private renderer: Renderer2,
|
||||||
|
private permissionsService: PermissionsService
|
||||||
) {
|
) {
|
||||||
let anyWindow = window as any
|
let anyWindow = window as any
|
||||||
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
|
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
|
||||||
@ -74,15 +80,28 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
if (
|
if (
|
||||||
this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)
|
this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)
|
||||||
) {
|
) {
|
||||||
this.toastService.show({
|
if (
|
||||||
title: $localize`Document added`,
|
this.permissionsService.currentUserCan(
|
||||||
delay: 10000,
|
PermissionAction.View,
|
||||||
content: $localize`Document ${status.filename} was added to paperless.`,
|
PermissionType.Document
|
||||||
actionName: $localize`Open document`,
|
)
|
||||||
action: () => {
|
) {
|
||||||
this.router.navigate(['documents', status.documentId])
|
this.toastService.show({
|
||||||
},
|
title: $localize`Document added`,
|
||||||
})
|
delay: 10000,
|
||||||
|
content: $localize`Document ${status.filename} was added to paperless.`,
|
||||||
|
actionName: $localize`Open document`,
|
||||||
|
action: () => {
|
||||||
|
this.router.navigate(['documents', status.documentId])
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.toastService.show({
|
||||||
|
title: $localize`Document added`,
|
||||||
|
delay: 10000,
|
||||||
|
content: $localize`Document ${status.filename} was added to paperless.`,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -225,7 +244,13 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get dragDropEnabled(): boolean {
|
public get dragDropEnabled(): boolean {
|
||||||
return !this.router.url.includes('dashboard')
|
return (
|
||||||
|
!this.router.url.includes('dashboard') &&
|
||||||
|
this.permissionsService.currentUserCan(
|
||||||
|
PermissionAction.Add,
|
||||||
|
PermissionType.Document
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fileOver() {
|
public fileOver() {
|
||||||
|
@ -42,6 +42,7 @@ import { CheckComponent } from './components/common/input/check/check.component'
|
|||||||
import { PasswordComponent } from './components/common/input/password/password.component'
|
import { PasswordComponent } from './components/common/input/password/password.component'
|
||||||
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'
|
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'
|
||||||
import { TagsComponent } from './components/common/input/tags/tags.component'
|
import { TagsComponent } from './components/common/input/tags/tags.component'
|
||||||
|
import { IfPermissionsDirective } from './directives/if-permissions.directive'
|
||||||
import { SortableDirective } from './directives/sortable.directive'
|
import { SortableDirective } from './directives/sortable.directive'
|
||||||
import { CookieService } from 'ngx-cookie-service'
|
import { CookieService } from 'ngx-cookie-service'
|
||||||
import { CsrfInterceptor } from './interceptors/csrf.interceptor'
|
import { CsrfInterceptor } from './interceptors/csrf.interceptor'
|
||||||
@ -70,6 +71,7 @@ import { ColorSliderModule } from 'ngx-color/slider'
|
|||||||
import { ColorComponent } from './components/common/input/color/color.component'
|
import { ColorComponent } from './components/common/input/color/color.component'
|
||||||
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
|
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
|
||||||
import { DocumentCommentsComponent } from './components/document-comments/document-comments.component'
|
import { DocumentCommentsComponent } from './components/document-comments/document-comments.component'
|
||||||
|
import { PermissionsGuard } from './guards/permissions.guard'
|
||||||
import { DirtyDocGuard } from './guards/dirty-doc.guard'
|
import { DirtyDocGuard } from './guards/dirty-doc.guard'
|
||||||
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
|
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
|
||||||
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
|
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
|
||||||
@ -77,8 +79,15 @@ import { StoragePathEditDialogComponent } from './components/common/edit-dialog/
|
|||||||
import { SettingsService } from './services/settings.service'
|
import { SettingsService } from './services/settings.service'
|
||||||
import { TasksComponent } from './components/manage/tasks/tasks.component'
|
import { TasksComponent } from './components/manage/tasks/tasks.component'
|
||||||
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
|
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
|
||||||
|
import { UserEditDialogComponent } from './components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
||||||
|
import { GroupEditDialogComponent } from './components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||||
|
import { PermissionsSelectComponent } from './components/common/permissions-select/permissions-select.component'
|
||||||
import { MailAccountEditDialogComponent } from './components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
import { MailAccountEditDialogComponent } from './components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
||||||
import { MailRuleEditDialogComponent } from './components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
import { MailRuleEditDialogComponent } from './components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
||||||
|
import { PermissionsUserComponent } from './components/common/input/permissions/permissions-user/permissions-user.component'
|
||||||
|
import { PermissionsGroupComponent } from './components/common/input/permissions/permissions-group/permissions-group.component'
|
||||||
|
import { IfOwnerDirective } from './directives/if-owner.directive'
|
||||||
|
import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive'
|
||||||
|
|
||||||
import localeAr from '@angular/common/locales/ar'
|
import localeAr from '@angular/common/locales/ar'
|
||||||
import localeBe from '@angular/common/locales/be'
|
import localeBe from '@angular/common/locales/be'
|
||||||
@ -100,6 +109,8 @@ import localeSr from '@angular/common/locales/sr'
|
|||||||
import localeSv from '@angular/common/locales/sv'
|
import localeSv from '@angular/common/locales/sv'
|
||||||
import localeTr from '@angular/common/locales/tr'
|
import localeTr from '@angular/common/locales/tr'
|
||||||
import localeZh from '@angular/common/locales/zh'
|
import localeZh from '@angular/common/locales/zh'
|
||||||
|
import { PermissionsDialogComponent } from './components/common/permissions-dialog/permissions-dialog.component'
|
||||||
|
import { PermissionsFormComponent } from './components/common/input/permissions/permissions-form/permissions-form.component'
|
||||||
|
|
||||||
registerLocaleData(localeAr)
|
registerLocaleData(localeAr)
|
||||||
registerLocaleData(localeBe)
|
registerLocaleData(localeBe)
|
||||||
@ -165,6 +176,7 @@ function initializeApp(settings: SettingsService) {
|
|||||||
PasswordComponent,
|
PasswordComponent,
|
||||||
SaveViewConfigDialogComponent,
|
SaveViewConfigDialogComponent,
|
||||||
TagsComponent,
|
TagsComponent,
|
||||||
|
IfPermissionsDirective,
|
||||||
SortableDirective,
|
SortableDirective,
|
||||||
SavedViewWidgetComponent,
|
SavedViewWidgetComponent,
|
||||||
StatisticsWidgetComponent,
|
StatisticsWidgetComponent,
|
||||||
@ -186,8 +198,17 @@ function initializeApp(settings: SettingsService) {
|
|||||||
DocumentAsnComponent,
|
DocumentAsnComponent,
|
||||||
DocumentCommentsComponent,
|
DocumentCommentsComponent,
|
||||||
TasksComponent,
|
TasksComponent,
|
||||||
|
UserEditDialogComponent,
|
||||||
|
GroupEditDialogComponent,
|
||||||
|
PermissionsSelectComponent,
|
||||||
MailAccountEditDialogComponent,
|
MailAccountEditDialogComponent,
|
||||||
MailRuleEditDialogComponent,
|
MailRuleEditDialogComponent,
|
||||||
|
PermissionsUserComponent,
|
||||||
|
PermissionsGroupComponent,
|
||||||
|
IfOwnerDirective,
|
||||||
|
IfObjectPermissionsDirective,
|
||||||
|
PermissionsDialogComponent,
|
||||||
|
PermissionsFormComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -225,6 +246,7 @@ function initializeApp(settings: SettingsService) {
|
|||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
{ provide: NgbDateAdapter, useClass: ISODateAdapter },
|
{ provide: NgbDateAdapter, useClass: ISODateAdapter },
|
||||||
{ provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter },
|
{ provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter },
|
||||||
|
PermissionsGuard,
|
||||||
DirtyDocGuard,
|
DirtyDocGuard,
|
||||||
DirtySavedViewGuard,
|
DirtySavedViewGuard,
|
||||||
],
|
],
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="ms-2" [class.visually-hidden]="slimSidebarEnabled" i18n="app title">Paperless-ngx</span>
|
<span class="ms-2" [class.visually-hidden]="slimSidebarEnabled" i18n="app title">Paperless-ngx</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="search-form-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1">
|
<div class="search-form-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||||
<form (ngSubmit)="search()" class="form-inline flex-grow-1">
|
<form (ngSubmit)="search()" class="form-inline flex-grow-1">
|
||||||
<svg width="1em" height="1em" fill="currentColor">
|
<svg width="1em" height="1em" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#search"/>
|
<use xlink:href="assets/bootstrap-icons.svg#search"/>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<p class="small mb-0 px-3 text-muted" i18n>Logged in as {{this.settingsService.displayName}}</p>
|
<p class="small mb-0 px-3 text-muted" i18n>Logged in as {{this.settingsService.displayName}}</p>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
</div>
|
</div>
|
||||||
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()">
|
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }">
|
||||||
<svg class="sidebaricon me-2" fill="currentColor">
|
<svg class="sidebaricon me-2" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||||
</svg><ng-container i18n>Settings</ng-container>
|
</svg><ng-container i18n>Settings</ng-container>
|
||||||
@ -72,7 +72,7 @@
|
|||||||
</svg><span> <ng-container i18n>Dashboard</ng-container></span>
|
</svg><span> <ng-container i18n>Dashboard</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||||
<a class="nav-link" routerLink="documents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Documents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="documents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Documents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#files"/>
|
<use xlink:href="assets/bootstrap-icons.svg#files"/>
|
||||||
@ -80,79 +80,82 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<div *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||||
|
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='savedViewService.loading || savedViewService.sidebarViews.length > 0'>
|
||||||
|
<span i18n>Saved views</span>
|
||||||
|
<div *ngIf="savedViewService.loading" class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
|
||||||
|
</h6>
|
||||||
|
<ul class="nav flex-column mb-2">
|
||||||
|
<li class="nav-item w-100" *ngFor="let view of savedViewService.sidebarViews">
|
||||||
|
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#funnel"/>
|
||||||
|
</svg><span> {{view.name}}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='savedViewService.loading || savedViewService.sidebarViews.length > 0'>
|
<div *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||||
<span i18n>Saved views</span>
|
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
|
||||||
<div *ngIf="savedViewService.loading" class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
|
<span i18n>Open documents</span>
|
||||||
</h6>
|
</h6>
|
||||||
<ul class="nav flex-column mb-2">
|
<ul class="nav flex-column mb-2">
|
||||||
<li class="nav-item w-100" *ngFor="let view of savedViewService.sidebarViews">
|
<li class="nav-item w-100" *ngFor='let d of openDocuments'>
|
||||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="documents/{{d.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="d.title | documentTitle" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#funnel"/>
|
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||||
</svg><span> {{view.name}}</span>
|
</svg><span> {{d.title | documentTitle}}</span>
|
||||||
</a>
|
<span class="close" (click)="closeDocument(d); $event.preventDefault()">
|
||||||
</li>
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16">
|
||||||
</ul>
|
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||||
|
</svg>
|
||||||
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
|
</span>
|
||||||
<span i18n>Open documents</span>
|
</a>
|
||||||
</h6>
|
</li>
|
||||||
<ul class="nav flex-column mb-2">
|
<li class="nav-item w-100" *ngIf="openDocuments.length >= 1">
|
||||||
<li class="nav-item w-100" *ngFor='let d of openDocuments'>
|
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" [routerLink]="[]" (click)="closeAll()" ngbPopover="Close all" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="documents/{{d.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="d.title | documentTitle" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
|
||||||
</svg><span> {{d.title | documentTitle}}</span>
|
|
||||||
<span class="close" (click)="closeDocument(d); $event.preventDefault()">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16">
|
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||||
</svg>
|
</svg><span> <ng-container i18n>Close all</ng-container></span>
|
||||||
</span>
|
</a>
|
||||||
</a>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
<li class="nav-item w-100" *ngIf="openDocuments.length >= 1">
|
</div>
|
||||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" [routerLink]="[]" (click)="closeAll()" ngbPopover="Close all" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
|
||||||
</svg><span> <ng-container i18n>Close all</ng-container></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted">
|
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted">
|
||||||
<span i18n>Manage</span>
|
<span i18n>Manage</span>
|
||||||
</h6>
|
</h6>
|
||||||
<ul class="nav flex-column mb-2">
|
<ul class="nav flex-column mb-2">
|
||||||
<li class="nav-item">
|
<li class="nav-item" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
||||||
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Correspondents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Correspondents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#person"/>
|
<use xlink:href="assets/bootstrap-icons.svg#person"/>
|
||||||
</svg><span> <ng-container i18n>Correspondents</ng-container></span>
|
</svg><span> <ng-container i18n>Correspondents</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" tourAnchor="tour.tags">
|
<li class="nav-item" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }" tourAnchor="tour.tags">
|
||||||
<a class="nav-link" routerLink="tags" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Tags" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="tags" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Tags" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#tags"/>
|
<use xlink:href="assets/bootstrap-icons.svg#tags"/>
|
||||||
</svg><span> <ng-container i18n>Tags</ng-container></span>
|
</svg><span> <ng-container i18n>Tags</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||||
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Document types" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Document types" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
|
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
|
||||||
</svg><span> <ng-container i18n>Document types</ng-container></span>
|
</svg><span> <ng-container i18n>Document types</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||||
<a class="nav-link" routerLink="storagepaths" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Storage paths" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="storagepaths" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Storage paths" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
|
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
|
||||||
</svg><span> <ng-container i18n>Storage paths</ng-container></span>
|
</svg><span> <ng-container i18n>Storage paths</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" tourAnchor="tour.file-tasks">
|
<li class="nav-item" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.PaperlessTask }" tourAnchor="tour.file-tasks">
|
||||||
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()" ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()" ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<span *ngIf="tasksService.failedFileTasks.length > 0 && slimSidebarEnabled" class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>
|
<span *ngIf="tasksService.failedFileTasks.length > 0 && slimSidebarEnabled" class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
@ -160,14 +163,14 @@
|
|||||||
</svg><span> <ng-container i18n>File Tasks<span *ngIf="tasksService.failedFileTasks.length > 0"><span class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></span></ng-container></span>
|
</svg><span> <ng-container i18n>File Tasks<span *ngIf="tasksService.failedFileTasks.length > 0"><span class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></span></ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }">
|
||||||
<a class="nav-link" routerLink="logs" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Logs" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="logs" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Logs" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#text-left"/>
|
<use xlink:href="assets/bootstrap-icons.svg#text-left"/>
|
||||||
</svg><span> <ng-container i18n>Logs</ng-container></span>
|
</svg><span> <ng-container i18n>Logs</ng-container></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" tourAnchor="tour.settings">
|
<li class="nav-item" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }" tourAnchor="tour.settings">
|
||||||
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
|
||||||
|
@ -26,13 +26,17 @@ import { TasksService } from 'src/app/services/tasks.service'
|
|||||||
import { ComponentCanDeactivate } from 'src/app/guards/dirty-doc.guard'
|
import { ComponentCanDeactivate } from 'src/app/guards/dirty-doc.guard'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
|
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-app-frame',
|
selector: 'app-app-frame',
|
||||||
templateUrl: './app-frame.component.html',
|
templateUrl: './app-frame.component.html',
|
||||||
styleUrls: ['./app-frame.component.scss'],
|
styleUrls: ['./app-frame.component.scss'],
|
||||||
})
|
})
|
||||||
export class AppFrameComponent implements OnInit, ComponentCanDeactivate {
|
export class AppFrameComponent
|
||||||
|
extends ComponentWithPermissions
|
||||||
|
implements OnInit, ComponentCanDeactivate
|
||||||
|
{
|
||||||
constructor(
|
constructor(
|
||||||
public router: Router,
|
public router: Router,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
@ -44,7 +48,9 @@ export class AppFrameComponent implements OnInit, ComponentCanDeactivate {
|
|||||||
public settingsService: SettingsService,
|
public settingsService: SettingsService,
|
||||||
public tasksService: TasksService,
|
public tasksService: TasksService,
|
||||||
private readonly toastService: ToastService
|
private readonly toastService: ToastService
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.settingsService.get(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)) {
|
if (this.settingsService.get(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { interval, Subject, switchMap, take } from 'rxjs'
|
import { interval, Subject, take } from 'rxjs'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-confirm-dialog',
|
selector: 'app-confirm-dialog',
|
||||||
|
@ -5,10 +5,16 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
|
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
|
||||||
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
|
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
|
||||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
||||||
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check>
|
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></app-input-check>
|
||||||
|
|
||||||
|
<div *appIfOwner="object">
|
||||||
|
<app-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></app-permissions-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||||
|
@ -5,6 +5,7 @@ import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-
|
|||||||
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
|
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
|
||||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-correspondent-edit-dialog',
|
selector: 'app-correspondent-edit-dialog',
|
||||||
@ -12,8 +13,12 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
|
|||||||
styleUrls: ['./correspondent-edit-dialog.component.scss'],
|
styleUrls: ['./correspondent-edit-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> {
|
export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> {
|
||||||
constructor(service: CorrespondentService, activeModal: NgbActiveModal) {
|
constructor(
|
||||||
super(service, activeModal)
|
service: CorrespondentService,
|
||||||
|
activeModal: NgbActiveModal,
|
||||||
|
userService: UserService
|
||||||
|
) {
|
||||||
|
super(service, activeModal, userService)
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateTitle() {
|
getCreateTitle() {
|
||||||
@ -30,6 +35,7 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl
|
|||||||
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
||||||
match: new FormControl(''),
|
match: new FormControl(''),
|
||||||
is_insensitive: new FormControl(true),
|
is_insensitive: new FormControl(true),
|
||||||
|
permissions_form: new FormControl(null),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
|
<div class="col">
|
||||||
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
|
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
|
||||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
|
||||||
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
|
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
||||||
|
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *appIfOwner="object">
|
||||||
|
<app-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></app-permissions-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
@ -5,6 +5,7 @@ import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-
|
|||||||
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
|
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
|
||||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-document-type-edit-dialog',
|
selector: 'app-document-type-edit-dialog',
|
||||||
@ -12,8 +13,12 @@ import { DocumentTypeService } from 'src/app/services/rest/document-type.service
|
|||||||
styleUrls: ['./document-type-edit-dialog.component.scss'],
|
styleUrls: ['./document-type-edit-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> {
|
export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> {
|
||||||
constructor(service: DocumentTypeService, activeModal: NgbActiveModal) {
|
constructor(
|
||||||
super(service, activeModal)
|
service: DocumentTypeService,
|
||||||
|
activeModal: NgbActiveModal,
|
||||||
|
userService: UserService
|
||||||
|
) {
|
||||||
|
super(service, activeModal, userService)
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateTitle() {
|
getCreateTitle() {
|
||||||
@ -30,6 +35,7 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent<Paperle
|
|||||||
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
||||||
match: new FormControl(''),
|
match: new FormControl(''),
|
||||||
is_insensitive: new FormControl(true),
|
is_insensitive: new FormControl(true),
|
||||||
|
permissions_form: new FormControl(null),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,17 +4,25 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'
|
import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'
|
||||||
import { ObjectWithId } from 'src/app/data/object-with-id'
|
import { ObjectWithId } from 'src/app/data/object-with-id'
|
||||||
|
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
|
||||||
|
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||||
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'
|
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
import { PermissionsFormObject } from '../input/permissions/permissions-form/permissions-form.component'
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class EditDialogComponent<T extends ObjectWithId>
|
export abstract class EditDialogComponent<
|
||||||
implements OnInit
|
T extends ObjectWithPermissions | ObjectWithId
|
||||||
|
> implements OnInit
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private service: AbstractPaperlessService<T>,
|
private service: AbstractPaperlessService<T>,
|
||||||
private activeModal: NgbActiveModal
|
private activeModal: NgbActiveModal,
|
||||||
|
private userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
users: PaperlessUser[]
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
dialogMode: string = 'create'
|
dialogMode: string = 'create'
|
||||||
|
|
||||||
@ -36,6 +44,14 @@ export abstract class EditDialogComponent<T extends ObjectWithId>
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (this.object != null) {
|
if (this.object != null) {
|
||||||
|
if (this.object['permissions']) {
|
||||||
|
this.object['set_permissions'] = this.object['permissions']
|
||||||
|
}
|
||||||
|
|
||||||
|
this.object['permissions_form'] = {
|
||||||
|
owner: (this.object as ObjectWithPermissions).owner,
|
||||||
|
set_permissions: (this.object as ObjectWithPermissions).permissions,
|
||||||
|
}
|
||||||
this.objectForm.patchValue(this.object)
|
this.objectForm.patchValue(this.object)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +59,8 @@ export abstract class EditDialogComponent<T extends ObjectWithId>
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.closeEnabled = true
|
this.closeEnabled = true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.userService.listAll().subscribe((r) => (this.users = r.results))
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateTitle() {
|
getCreateTitle() {
|
||||||
@ -77,10 +95,16 @@ export abstract class EditDialogComponent<T extends ObjectWithId>
|
|||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
var newObject = Object.assign(
|
const formValues = Object.assign({}, this.objectForm.value)
|
||||||
Object.assign({}, this.object),
|
const permissionsObject: PermissionsFormObject =
|
||||||
this.objectForm.value
|
this.objectForm.get('permissions_form')?.value
|
||||||
)
|
if (permissionsObject) {
|
||||||
|
formValues.owner = permissionsObject.owner
|
||||||
|
formValues.set_permissions = permissionsObject.set_permissions
|
||||||
|
delete formValues.permissions_form
|
||||||
|
}
|
||||||
|
|
||||||
|
var newObject = Object.assign(Object.assign({}, this.object), formValues)
|
||||||
var serverResponse: Observable<T>
|
var serverResponse: Observable<T>
|
||||||
switch (this.dialogMode) {
|
switch (this.dialogMode) {
|
||||||
case 'create':
|
case 'create':
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
<form [formGroup]="objectForm" (ngSubmit)="save()">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
||||||
|
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<app-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></app-input-text>
|
||||||
|
<app-permissions-select i18n-title title="Permissions" formControlName="permissions" [error]="error?.permissions"></app-permissions-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -0,0 +1,37 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
||||||
|
import { PaperlessGroup } from 'src/app/data/paperless-group'
|
||||||
|
import { GroupService } from 'src/app/services/rest/group.service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-group-edit-dialog',
|
||||||
|
templateUrl: './group-edit-dialog.component.html',
|
||||||
|
styleUrls: ['./group-edit-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class GroupEditDialogComponent extends EditDialogComponent<PaperlessGroup> {
|
||||||
|
constructor(
|
||||||
|
service: GroupService,
|
||||||
|
activeModal: NgbActiveModal,
|
||||||
|
userService: UserService
|
||||||
|
) {
|
||||||
|
super(service, activeModal, userService)
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreateTitle() {
|
||||||
|
return $localize`Create new user group`
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditTitle() {
|
||||||
|
return $localize`Edit user group`
|
||||||
|
}
|
||||||
|
|
||||||
|
getForm(): FormGroup {
|
||||||
|
return new FormGroup({
|
||||||
|
name: new FormControl(''),
|
||||||
|
permissions: new FormControl(null),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import {
|
|||||||
PaperlessMailAccount,
|
PaperlessMailAccount,
|
||||||
} from 'src/app/data/paperless-mail-account'
|
} from 'src/app/data/paperless-mail-account'
|
||||||
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
const IMAP_SECURITY_OPTIONS = [
|
const IMAP_SECURITY_OPTIONS = [
|
||||||
{ id: IMAPSecurity.None, name: $localize`No encryption` },
|
{ id: IMAPSecurity.None, name: $localize`No encryption` },
|
||||||
@ -20,8 +21,12 @@ const IMAP_SECURITY_OPTIONS = [
|
|||||||
styleUrls: ['./mail-account-edit-dialog.component.scss'],
|
styleUrls: ['./mail-account-edit-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class MailAccountEditDialogComponent extends EditDialogComponent<PaperlessMailAccount> {
|
export class MailAccountEditDialogComponent extends EditDialogComponent<PaperlessMailAccount> {
|
||||||
constructor(service: MailAccountService, activeModal: NgbActiveModal) {
|
constructor(
|
||||||
super(service, activeModal)
|
service: MailAccountService,
|
||||||
|
activeModal: NgbActiveModal,
|
||||||
|
userService: UserService
|
||||||
|
) {
|
||||||
|
super(service, activeModal, userService)
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateTitle() {
|
getCreateTitle() {
|
||||||
|
@ -18,6 +18,7 @@ import { CorrespondentService } from 'src/app/services/rest/correspondent.servic
|
|||||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||||
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
||||||
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
const ATTACHMENT_TYPE_OPTIONS = [
|
const ATTACHMENT_TYPE_OPTIONS = [
|
||||||
{
|
{
|
||||||
@ -113,9 +114,10 @@ export class MailRuleEditDialogComponent extends EditDialogComponent<PaperlessMa
|
|||||||
activeModal: NgbActiveModal,
|
activeModal: NgbActiveModal,
|
||||||
accountService: MailAccountService,
|
accountService: MailAccountService,
|
||||||
correspondentService: CorrespondentService,
|
correspondentService: CorrespondentService,
|
||||||
documentTypeService: DocumentTypeService
|
documentTypeService: DocumentTypeService,
|
||||||
|
userService: UserService
|
||||||
) {
|
) {
|
||||||
super(service, activeModal)
|
super(service, activeModal, userService)
|
||||||
|
|
||||||
accountService
|
accountService
|
||||||
.listAll()
|
.listAll()
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
||||||
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
|
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
|
||||||
|
|
||||||
|
<div *appIfOwner="object">
|
||||||
|
<app-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></app-permissions-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||||
|
@ -5,6 +5,7 @@ import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-
|
|||||||
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-storage-path-edit-dialog',
|
selector: 'app-storage-path-edit-dialog',
|
||||||
@ -12,8 +13,12 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
|||||||
styleUrls: ['./storage-path-edit-dialog.component.scss'],
|
styleUrls: ['./storage-path-edit-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class StoragePathEditDialogComponent extends EditDialogComponent<PaperlessStoragePath> {
|
export class StoragePathEditDialogComponent extends EditDialogComponent<PaperlessStoragePath> {
|
||||||
constructor(service: StoragePathService, activeModal: NgbActiveModal) {
|
constructor(
|
||||||
super(service, activeModal)
|
service: StoragePathService,
|
||||||
|
activeModal: NgbActiveModal,
|
||||||
|
userService: UserService
|
||||||
|
) {
|
||||||
|
super(service, activeModal, userService)
|
||||||
}
|
}
|
||||||
|
|
||||||
get pathHint() {
|
get pathHint() {
|
||||||
@ -41,6 +46,7 @@ export class StoragePathEditDialogComponent extends EditDialogComponent<Paperles
|
|||||||
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
||||||
match: new FormControl(''),
|
match: new FormControl(''),
|
||||||
is_insensitive: new FormControl(true),
|
is_insensitive: new FormControl(true),
|
||||||
|
permissions_form: new FormControl(null),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,11 @@
|
|||||||
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
|
<app-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></app-input-select>
|
||||||
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
<app-input-text *ngIf="patternRequired" i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></app-input-text>
|
||||||
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
|
<app-input-check *ngIf="patternRequired" i18n-title title="Case insensitive" formControlName="is_insensitive"></app-input-check>
|
||||||
|
|
||||||
|
<div *appIfOwner="object">
|
||||||
|
<app-permissions-form [users]="users" accordion="true" formControlName="permissions_form"></app-permissions-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||||
|
@ -6,6 +6,7 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'
|
|||||||
import { TagService } from 'src/app/services/rest/tag.service'
|
import { TagService } from 'src/app/services/rest/tag.service'
|
||||||
import { randomColor } from 'src/app/utils/color'
|
import { randomColor } from 'src/app/utils/color'
|
||||||
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tag-edit-dialog',
|
selector: 'app-tag-edit-dialog',
|
||||||
@ -13,8 +14,12 @@ import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
|||||||
styleUrls: ['./tag-edit-dialog.component.scss'],
|
styleUrls: ['./tag-edit-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
|
export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
|
||||||
constructor(service: TagService, activeModal: NgbActiveModal) {
|
constructor(
|
||||||
super(service, activeModal)
|
service: TagService,
|
||||||
|
activeModal: NgbActiveModal,
|
||||||
|
userService: UserService
|
||||||
|
) {
|
||||||
|
super(service, activeModal, userService)
|
||||||
}
|
}
|
||||||
|
|
||||||
getCreateTitle() {
|
getCreateTitle() {
|
||||||
@ -33,6 +38,7 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
|
|||||||
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
||||||
match: new FormControl(''),
|
match: new FormControl(''),
|
||||||
is_insensitive: new FormControl(true),
|
is_insensitive: new FormControl(true),
|
||||||
|
permissions_form: new FormControl(null),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
<form [formGroup]="objectForm" (ngSubmit)="save()">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
|
||||||
|
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<app-input-text i18n-title title="Username" formControlName="username" [error]="error?.username"></app-input-text>
|
||||||
|
<app-input-text i18n-title title="Email" formControlName="email" [error]="error?.email"></app-input-text>
|
||||||
|
<app-input-password i18n-title title="Password" formControlName="password" [error]="error?.password"></app-input-password>
|
||||||
|
<app-input-text i18n-title title="First name" formControlName="first_name" [error]="error?.first_name"></app-input-text>
|
||||||
|
<app-input-text i18n-title title="Last name" formControlName="last_name" [error]="error?.first_name"></app-input-text>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<div class="form-check form-switch form-check-inline">
|
||||||
|
<input type="checkbox" class="form-check-input" id="is_active" formControlName="is_active">
|
||||||
|
<label class="form-check-label" for="is_active" i18n>Active</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch form-check-inline">
|
||||||
|
<input type="checkbox" class="form-check-input" id="is_superuser" formControlName="is_superuser" (change)="onToggleSuperUser()">
|
||||||
|
<label class="form-check-label" for="is_superuser"><ng-container i18n>Superuser</ng-container> <small class="form-text text-muted ms-1" i18n>(Grants all permissions and can view objects)</small></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<app-input-select i18n-title title="Groups" [items]="groups" multiple="true" formControlName="groups"></app-input-select>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<app-permissions-select i18n-title title="Permissions" formControlName="user_permissions" [error]="error?.user_permissions" [inheritedPermissions]="inheritedPermissions"></app-permissions-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@ -0,0 +1,79 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core'
|
||||||
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { first } from 'rxjs'
|
||||||
|
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
||||||
|
import { PaperlessGroup } from 'src/app/data/paperless-group'
|
||||||
|
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||||
|
import { GroupService } from 'src/app/services/rest/group.service'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-edit-dialog',
|
||||||
|
templateUrl: './user-edit-dialog.component.html',
|
||||||
|
styleUrls: ['./user-edit-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class UserEditDialogComponent
|
||||||
|
extends EditDialogComponent<PaperlessUser>
|
||||||
|
implements OnInit
|
||||||
|
{
|
||||||
|
groups: PaperlessGroup[]
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
service: UserService,
|
||||||
|
activeModal: NgbActiveModal,
|
||||||
|
groupsService: GroupService
|
||||||
|
) {
|
||||||
|
super(service, activeModal, service)
|
||||||
|
|
||||||
|
groupsService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((result) => (this.groups = result.results))
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit()
|
||||||
|
this.onToggleSuperUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreateTitle() {
|
||||||
|
return $localize`Create new user account`
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditTitle() {
|
||||||
|
return $localize`Edit user account`
|
||||||
|
}
|
||||||
|
|
||||||
|
getForm(): FormGroup {
|
||||||
|
return new FormGroup({
|
||||||
|
username: new FormControl(''),
|
||||||
|
email: new FormControl(''),
|
||||||
|
password: new FormControl(null),
|
||||||
|
first_name: new FormControl(''),
|
||||||
|
last_name: new FormControl(''),
|
||||||
|
is_active: new FormControl(true),
|
||||||
|
is_superuser: new FormControl(false),
|
||||||
|
groups: new FormControl([]),
|
||||||
|
user_permissions: new FormControl([]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleSuperUser() {
|
||||||
|
if (this.objectForm.get('is_superuser').value) {
|
||||||
|
this.objectForm.get('user_permissions').disable()
|
||||||
|
} else {
|
||||||
|
this.objectForm.get('user_permissions').enable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get inheritedPermissions(): string[] {
|
||||||
|
const groupsVal: Array<number> = this.objectForm.get('groups').value
|
||||||
|
|
||||||
|
if (!groupsVal) return []
|
||||||
|
else
|
||||||
|
return groupsVal.flatMap(
|
||||||
|
(id) => this.groups.find((g) => g.id == id)?.permissions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown">
|
<div class="btn-group w-100" ngbDropdown role="group" (openChange)="dropdownOpenChange($event)" #dropdown="ngbDropdown">
|
||||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'">
|
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="!editing && selectionModel.selectionSize() > 0 ? 'btn-primary' : 'btn-outline-primary'" [disabled]="disabled">
|
||||||
<svg class="toolbaricon" fill="currentColor">
|
<svg class="toolbaricon" fill="currentColor">
|
||||||
<use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
|
<use attr.xlink:href="assets/bootstrap-icons.svg#{{icon}}" />
|
||||||
</svg>
|
</svg>
|
||||||
@ -25,10 +25,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div *ngIf="selectionModel.items" class="items">
|
<div *ngIf="selectionModel.items" class="items">
|
||||||
<ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText">
|
<ng-container *ngFor="let item of selectionModel.itemsSorted | filter: filterText">
|
||||||
<app-toggleable-dropdown-button *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)"></app-toggleable-dropdown-button>
|
<app-toggleable-dropdown-button *ngIf="allowSelectNone || item.id" [item]="item" [state]="selectionModel.get(item.id)" (toggle)="selectionModel.toggle(item.id)" (exclude)="excludeClicked(item.id)" [disabled]="disabled"></app-toggleable-dropdown-button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<button *ngIf="editing" class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!modelIsDirty">
|
<button *ngIf="editing" class="list-group-item list-group-item-action bg-light" (click)="applyClicked()" [disabled]="!modelIsDirty || disabled">
|
||||||
<small class="ms-2" [ngClass]="{'fw-bold': modelIsDirty}" i18n>Apply</small>
|
<small class="ms-2" [ngClass]="{'fw-bold': modelIsDirty}" i18n>Apply</small>
|
||||||
<svg width="1.5em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
<svg width="1.5em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
|
<use xlink:href="assets/bootstrap-icons.svg#arrow-right" />
|
||||||
|
@ -317,6 +317,9 @@ export class FilterableDropdownComponent {
|
|||||||
@Input()
|
@Input()
|
||||||
applyOnClose = false
|
applyOnClose = false
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
disabled = false
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
apply = new EventEmitter<ChangedItems>()
|
apply = new EventEmitter<ChangedItems>()
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="toggleItem($event)">
|
<button class="list-group-item list-group-item-action d-flex align-items-center p-2 border-top-0 border-start-0 border-end-0 border-bottom" role="menuitem" (click)="toggleItem($event)" [disabled]="disabled">
|
||||||
<div class="selected-icon me-1">
|
<div class="selected-icon me-1">
|
||||||
<ng-container *ngIf="isChecked()">
|
<ng-container *ngIf="isChecked()">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" class="bi bi-check" viewBox="0 0 16 16">
|
||||||
|
@ -23,6 +23,9 @@ export class ToggleableDropdownButtonComponent {
|
|||||||
@Input()
|
@Input()
|
||||||
count: number
|
count: number
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
disabled: boolean = false
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
toggle = new EventEmitter()
|
toggle = new EventEmitter()
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
<div class="input-group" [class.is-invalid]="error">
|
<div class="input-group" [class.is-invalid]="error">
|
||||||
<input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" maxlength="10"
|
<input class="form-control" [class.is-invalid]="error" [placeholder]="placeholder" [id]="inputId" maxlength="10"
|
||||||
(dateSelect)="onChange(value)" (change)="onChange(value)" (keypress)="onKeyPress($event)" (paste)="onPaste($event)"
|
(dateSelect)="onChange(value)" (change)="onChange(value)" (keypress)="onKeyPress($event)" (paste)="onPaste($event)"
|
||||||
name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel">
|
name="dp" [(ngModel)]="value" ngbDatepicker #datePicker="ngbDatepicker" #datePickerContent="ngModel" [disabled]="disabled">
|
||||||
<button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button">
|
<button class="btn btn-outline-secondary calendar" (click)="datePicker.toggle()" type="button" [disabled]="disabled">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-calendar" viewBox="0 0 16 16">
|
||||||
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" [for]="inputId">{{title}}</label>
|
<label class="form-label" [for]="inputId">{{title}}</label>
|
||||||
<div class="input-group" [class.is-invalid]="error">
|
<div class="input-group" [class.is-invalid]="error">
|
||||||
<input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error">
|
<input type="number" class="form-control" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
|
||||||
<button *ngIf="showAdd" class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="value">+1</button>
|
<button *ngIf="showAdd" class="btn btn-outline-secondary" type="button" id="button-addon1" (click)="nextAsn()" [disabled]="disabled">+1</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
{{error}}
|
{{error}}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
<ng-container *ngIf="!accordion">
|
||||||
|
<h5 i18n>Permissions</h5>
|
||||||
|
<ng-container [ngTemplateOutlet]="permissionsForm"></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="accordion">
|
||||||
|
<ngb-accordion #acc="ngbAccordion" activeIds="">
|
||||||
|
<ngb-panel i18n-title title="Edit Permissions">
|
||||||
|
<ng-template ngbPanelContent>
|
||||||
|
<ng-container [ngTemplateOutlet]="permissionsForm"></ng-container>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-panel>
|
||||||
|
</ngb-accordion>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #permissionsForm>
|
||||||
|
<div [formGroup]="form">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<label class="form-label d-block my-2" i18n>Owner:</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<app-input-select [items]="users" bindLabel="username" formControlName="owner" [allowNull]="true"></app-input-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted text-end d-block mt-n2" i18n>Objects without an owner can be viewed and edited by all users</small>
|
||||||
|
<div formGroupName="set_permissions">
|
||||||
|
<h6 class="mt-3" i18n>View</h6>
|
||||||
|
<div formGroupName="view" class="mb-2">
|
||||||
|
<div class="row mb-1">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<label class="form-label d-block my-2" i18n>Users:</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<app-permissions-user type="view" formControlName="users"></app-permissions-user>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<label class="form-label d-block my-2" i18n>Groups:</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<app-permissions-group type="view" formControlName="groups"></app-permissions-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h6 class="mt-4" i18n>Edit</h6>
|
||||||
|
<div formGroupName="change">
|
||||||
|
<div class="row mb-1">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<label class="form-label d-block my-2" i18n>Users:</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<app-permissions-user type="change" formControlName="users"></app-permissions-user>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<label class="form-label d-block my-2" i18n>Groups:</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
<app-permissions-group type="change" formControlName="groups"></app-permissions-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted text-end d-block" i18n>Edit permissions also grant viewing permissions</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
@ -0,0 +1,69 @@
|
|||||||
|
import { Component, forwardRef, Input, OnInit } from '@angular/core'
|
||||||
|
import { FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||||
|
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||||
|
import { AbstractInputComponent } from '../../abstract-input'
|
||||||
|
|
||||||
|
export interface PermissionsFormObject {
|
||||||
|
owner?: number
|
||||||
|
set_permissions?: {
|
||||||
|
view?: {
|
||||||
|
users?: number[]
|
||||||
|
groups?: number[]
|
||||||
|
}
|
||||||
|
change?: {
|
||||||
|
users?: number[]
|
||||||
|
groups?: number[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => PermissionsFormComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selector: 'app-permissions-form',
|
||||||
|
templateUrl: './permissions-form.component.html',
|
||||||
|
styleUrls: ['./permissions-form.component.scss'],
|
||||||
|
})
|
||||||
|
export class PermissionsFormComponent
|
||||||
|
extends AbstractInputComponent<PermissionsFormObject>
|
||||||
|
implements OnInit
|
||||||
|
{
|
||||||
|
@Input()
|
||||||
|
users: PaperlessUser[]
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
accordion: boolean = false
|
||||||
|
|
||||||
|
form = new FormGroup({
|
||||||
|
owner: new FormControl(null),
|
||||||
|
set_permissions: new FormGroup({
|
||||||
|
view: new FormGroup({
|
||||||
|
users: new FormControl([]),
|
||||||
|
groups: new FormControl([]),
|
||||||
|
}),
|
||||||
|
change: new FormGroup({
|
||||||
|
users: new FormControl([]),
|
||||||
|
groups: new FormControl([]),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.form.valueChanges.subscribe((value) => {
|
||||||
|
this.onChange(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
writeValue(newValue: any): void {
|
||||||
|
this.form.patchValue(newValue, { emitEvent: false })
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
<div class="paperless-input-select">
|
||||||
|
<div>
|
||||||
|
<ng-select name="inputId" [(ngModel)]="value"
|
||||||
|
[disabled]="disabled"
|
||||||
|
clearable="true"
|
||||||
|
[items]="groups"
|
||||||
|
multiple="true"
|
||||||
|
bindLabel="name"
|
||||||
|
bindValue="id"
|
||||||
|
(change)="onChange(value)">
|
||||||
|
</ng-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,30 @@
|
|||||||
|
import { Component, forwardRef, Input, OnInit } from '@angular/core'
|
||||||
|
import { NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||||
|
import { first } from 'rxjs/operators'
|
||||||
|
import { PaperlessGroup } from 'src/app/data/paperless-group'
|
||||||
|
import { GroupService } from 'src/app/services/rest/group.service'
|
||||||
|
import { AbstractInputComponent } from '../../abstract-input'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => PermissionsGroupComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selector: 'app-permissions-group',
|
||||||
|
templateUrl: './permissions-group.component.html',
|
||||||
|
styleUrls: ['./permissions-group.component.scss'],
|
||||||
|
})
|
||||||
|
export class PermissionsGroupComponent extends AbstractInputComponent<PaperlessGroup> {
|
||||||
|
groups: PaperlessGroup[]
|
||||||
|
|
||||||
|
constructor(groupService: GroupService) {
|
||||||
|
super()
|
||||||
|
groupService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((result) => (this.groups = result.results))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
<div class="paperless-input-select">
|
||||||
|
<div>
|
||||||
|
<ng-select name="inputId" [(ngModel)]="value"
|
||||||
|
[disabled]="disabled"
|
||||||
|
clearable="true"
|
||||||
|
[items]="users"
|
||||||
|
multiple="true"
|
||||||
|
bindLabel="username"
|
||||||
|
bindValue="id"
|
||||||
|
(change)="onChange(value)">
|
||||||
|
</ng-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,38 @@
|
|||||||
|
import { Component, forwardRef, Input, OnInit } from '@angular/core'
|
||||||
|
import { NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||||
|
import { first } from 'rxjs/operators'
|
||||||
|
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
|
import { AbstractInputComponent } from '../../abstract-input'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => PermissionsUserComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selector: 'app-permissions-user',
|
||||||
|
templateUrl: './permissions-user.component.html',
|
||||||
|
styleUrls: ['./permissions-user.component.scss'],
|
||||||
|
})
|
||||||
|
export class PermissionsUserComponent extends AbstractInputComponent<
|
||||||
|
PaperlessUser[]
|
||||||
|
> {
|
||||||
|
users: PaperlessUser[]
|
||||||
|
|
||||||
|
constructor(userService: UserService, settings: SettingsService) {
|
||||||
|
super()
|
||||||
|
userService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe(
|
||||||
|
(result) =>
|
||||||
|
(this.users = result.results.filter(
|
||||||
|
(u) => u.id !== settings.currentUser.id
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<div class="mb-3 paperless-input-select">
|
<div class="mb-3 paperless-input-select" [class.disabled]="disabled">
|
||||||
<label class="form-label" [for]="inputId">{{title}}</label>
|
<label *ngIf="title" class="form-label" [for]="inputId">{{title}}</label>
|
||||||
<div [class.input-group]="allowCreateNew">
|
<div [class.input-group]="allowCreateNew">
|
||||||
<ng-select name="inputId" [(ngModel)]="value"
|
<ng-select name="inputId" [(ngModel)]="value"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
@ -11,7 +11,8 @@
|
|||||||
addTagText="Add item"
|
addTagText="Add item"
|
||||||
i18n-addTagText="Used for both types, correspondents, storage paths"
|
i18n-addTagText="Used for both types, correspondents, storage paths"
|
||||||
[placeholder]="placeholder"
|
[placeholder]="placeholder"
|
||||||
bindLabel="name"
|
[multiple]="multiple"
|
||||||
|
[bindLabel]="bindLabel"
|
||||||
bindValue="id"
|
bindValue="id"
|
||||||
(change)="onChange(value)"
|
(change)="onChange(value)"
|
||||||
(search)="onSearch($event)"
|
(search)="onSearch($event)"
|
||||||
@ -19,7 +20,7 @@
|
|||||||
(clear)="clearLastSearchTerm()"
|
(clear)="clearLastSearchTerm()"
|
||||||
(blur)="onBlur()">
|
(blur)="onBlur()">
|
||||||
</ng-select>
|
</ng-select>
|
||||||
<button *ngIf="allowCreateNew" class="btn btn-outline-secondary" type="button" (click)="addItem()">
|
<button *ngIf="allowCreateNew" class="btn btn-outline-secondary" type="button" (click)="addItem()" [disabled]="disabled">
|
||||||
<svg class="buttonicon" fill="currentColor">
|
<svg class="buttonicon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#plus" />
|
<use xlink:href="assets/bootstrap-icons.svg#plus" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -1 +1,14 @@
|
|||||||
// styles for ng-select child are in styles.scss
|
// styles for ng-select child are in styles.scss
|
||||||
|
.paperless-input-select.disabled {
|
||||||
|
.input-group {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep ng-select {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.ng-select-container {
|
||||||
|
background-color: var(--pngx-bg-alt) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -44,6 +44,12 @@ export class SelectComponent extends AbstractInputComponent<number> {
|
|||||||
@Input()
|
@Input()
|
||||||
placeholder: string
|
placeholder: string
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
multiple: boolean = false
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
bindLabel: string = 'name'
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
createNew = new EventEmitter<string>()
|
createNew = new EventEmitter<string>()
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
<div class="mb-3 paperless-input-select paperless-input-tags">
|
<div class="mb-3 paperless-input-select paperless-input-tags" [class.disabled]="disabled">
|
||||||
<label class="form-label" for="tags" i18n>Tags</label>
|
<label class="form-label" for="tags" i18n>Tags</label>
|
||||||
|
|
||||||
<div class="input-group flex-nowrap">
|
<div class="input-group flex-nowrap">
|
||||||
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="value"
|
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="value"
|
||||||
|
[disabled]="disabled"
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
[closeOnSelect]="false"
|
[closeOnSelect]="false"
|
||||||
[clearSearchOnAdd]="true"
|
[clearSearchOnAdd]="true"
|
||||||
@ -31,7 +32,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-select>
|
</ng-select>
|
||||||
|
|
||||||
<button *ngIf="allowCreate" class="btn btn-outline-secondary" type="button" (click)="createTag()">
|
<button *ngIf="allowCreate" class="btn btn-outline-secondary" type="button" (click)="createTag()" [disabled]="disabled">
|
||||||
<svg class="buttonicon" fill="currentColor">
|
<svg class="buttonicon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#plus" />
|
<use xlink:href="assets/bootstrap-icons.svg#plus" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -10,3 +10,17 @@
|
|||||||
.tag-wrap-delete {
|
.tag-wrap-delete {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paperless-input-select.disabled {
|
||||||
|
.input-group {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep ng-select {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.ng-select-container {
|
||||||
|
background-color: var(--pngx-bg-alt) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -74,6 +74,8 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeTag(event: PointerEvent, id: number) {
|
removeTag(event: PointerEvent, id: number) {
|
||||||
|
if (this.disabled) return
|
||||||
|
|
||||||
// prevent opening dropdown
|
// prevent opening dropdown
|
||||||
event.stopImmediatePropagation()
|
event.stopImmediatePropagation()
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" [for]="inputId">{{title}}</label>
|
<label class="form-label" [for]="inputId">{{title}}</label>
|
||||||
<input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)">
|
<input #inputField type="text" class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [disabled]="disabled">
|
||||||
<small *ngIf="hint" class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
|
<small *ngIf="hint" class="form-text text-muted" [innerHTML]="hint | safeHtml"></small>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
{{error}}
|
{{error}}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
|
||||||
|
<button type="button" class="btn-close" aria-label="Close" (click)="cancelClicked()">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<p class="mb-3" *ngIf="message" [innerHTML]="message | safeHtml"></p>
|
||||||
|
|
||||||
|
<form [formGroup]="form">
|
||||||
|
<app-permissions-form [users]="users" formControlName="permissions_form"></app-permissions-form>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-primary" (click)="cancelClicked()" i18n>Cancel</button>
|
||||||
|
<button type="button" class="btn btn-primary" (click)="confirmClicked.emit(permissions)" i18n>Confirm</button>
|
||||||
|
</div>
|
@ -0,0 +1,46 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||||
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-permissions-dialog',
|
||||||
|
templateUrl: './permissions-dialog.component.html',
|
||||||
|
styleUrls: ['./permissions-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class PermissionsDialogComponent {
|
||||||
|
users: PaperlessUser[]
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public activeModal: NgbActiveModal,
|
||||||
|
private userService: UserService
|
||||||
|
) {
|
||||||
|
this.userService.listAll().subscribe((r) => (this.users = r.results))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
public confirmClicked = new EventEmitter()
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
title = $localize`Set Permissions`
|
||||||
|
|
||||||
|
form = new FormGroup({
|
||||||
|
permissions_form: new FormControl(),
|
||||||
|
})
|
||||||
|
|
||||||
|
get permissions() {
|
||||||
|
return {
|
||||||
|
owner: this.form.get('permissions_form').value?.owner ?? null,
|
||||||
|
set_permissions:
|
||||||
|
this.form.get('permissions_form').value?.set_permissions ?? null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
message = $localize`Note that permissions set here will override any existing permissions`
|
||||||
|
|
||||||
|
cancelClicked() {
|
||||||
|
this.activeModal.close()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<form [formGroup]="form" [class.opacity-50]="disabled">
|
||||||
|
<label class="form-label">{{title}}</label>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item d-flex">
|
||||||
|
<div class="col-3" i18n>Type</div>
|
||||||
|
<div class="col" i18n>All</div>
|
||||||
|
<div class="col" i18n>Add</div>
|
||||||
|
<div class="col" i18n>Change</div>
|
||||||
|
<div class="col" i18n>Delete</div>
|
||||||
|
<div class="col" i18n>View</div>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item d-flex" *ngFor="let type of PermissionType | keyvalue" [formGroupName]="type.key">
|
||||||
|
<div class="col-3">{{type.key}}:</div>
|
||||||
|
|
||||||
|
<div class="col form-check form-check-inline form-switch" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key)" placement="left" triggers="mouseenter:mouseleave">
|
||||||
|
<input type="checkbox" class="form-check-input" id="{{type.key}}_all" (change)="toggleAll($event, type.key)" [checked]="typesWithAllActions.has(type.key) || isInherited(type.key)" [attr.disabled]="disabled || isInherited(type.key) ? true : null">
|
||||||
|
<label class="form-check-label visually-hidden" for="{{type.key}}_all" i18n>All</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngFor="let action of PermissionAction | keyvalue" class="col form-check form-check-inline" [ngbPopover]="inheritedWarning" [disablePopover]="!isInherited(type.key, action.key)" placement="left" triggers="mouseenter:mouseleave">
|
||||||
|
<input type="checkbox" class="form-check-input" id="{{type.key}}_{{action.key}}" formControlName="{{action.key}}" [attr.disabled]="isDisabled(type.key, action.key)">
|
||||||
|
<label class="form-check-label visually-hidden" for="{{type.key}}_{{action.key}}" i18n>{{action.key}}</label>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<div *ngIf="error" class="invalid-feedback d-block">{{error}}</div>
|
||||||
|
</ul>
|
||||||
|
</form>
|
@ -0,0 +1,189 @@
|
|||||||
|
import { Component, forwardRef, Input, OnInit } from '@angular/core'
|
||||||
|
import {
|
||||||
|
ControlValueAccessor,
|
||||||
|
FormControl,
|
||||||
|
FormGroup,
|
||||||
|
NG_VALUE_ACCESSOR,
|
||||||
|
} from '@angular/forms'
|
||||||
|
import {
|
||||||
|
PermissionAction,
|
||||||
|
PermissionsService,
|
||||||
|
PermissionType,
|
||||||
|
} from 'src/app/services/permissions.service'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => PermissionsSelectComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selector: 'app-permissions-select',
|
||||||
|
templateUrl: './permissions-select.component.html',
|
||||||
|
styleUrls: ['./permissions-select.component.scss'],
|
||||||
|
})
|
||||||
|
export class PermissionsSelectComponent
|
||||||
|
implements OnInit, ControlValueAccessor
|
||||||
|
{
|
||||||
|
PermissionType = PermissionType
|
||||||
|
PermissionAction = PermissionAction
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
title: string = 'Permissions'
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
error: string
|
||||||
|
|
||||||
|
permissions: string[]
|
||||||
|
|
||||||
|
form = new FormGroup({})
|
||||||
|
|
||||||
|
typesWithAllActions: Set<string> = new Set()
|
||||||
|
|
||||||
|
_inheritedPermissions: string[] = []
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set inheritedPermissions(inherited: string[]) {
|
||||||
|
// remove <app_label>. from permission strings
|
||||||
|
const newInheritedPermissions = inherited?.length
|
||||||
|
? inherited.map((p) => p.replace(/^\w+\./g, ''))
|
||||||
|
: []
|
||||||
|
|
||||||
|
if (this._inheritedPermissions !== newInheritedPermissions) {
|
||||||
|
this._inheritedPermissions = newInheritedPermissions
|
||||||
|
this.writeValue(this.permissions) // updates visual checks etc.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inheritedWarning: string = $localize`Inerhited from group`
|
||||||
|
|
||||||
|
constructor(private readonly permissionsService: PermissionsService) {
|
||||||
|
for (const type in PermissionType) {
|
||||||
|
const control = new FormGroup({})
|
||||||
|
for (const action in PermissionAction) {
|
||||||
|
control.addControl(action, new FormControl(null))
|
||||||
|
}
|
||||||
|
this.form.addControl(type, control)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeValue(permissions: string[]): void {
|
||||||
|
this.permissions = permissions ?? []
|
||||||
|
const allPerms = this._inheritedPermissions.concat(this.permissions)
|
||||||
|
|
||||||
|
allPerms.forEach((permissionStr) => {
|
||||||
|
const { actionKey, typeKey } =
|
||||||
|
this.permissionsService.getPermissionKeys(permissionStr)
|
||||||
|
|
||||||
|
if (actionKey && typeKey) {
|
||||||
|
if (this.form.get(typeKey)?.get(actionKey)) {
|
||||||
|
this.form
|
||||||
|
.get(typeKey)
|
||||||
|
.get(actionKey)
|
||||||
|
.patchValue(true, { emitEvent: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Object.keys(PermissionType).forEach((type) => {
|
||||||
|
if (
|
||||||
|
Object.values(this.form.get(type).value).every((val) => val == true)
|
||||||
|
) {
|
||||||
|
this.typesWithAllActions.add(type)
|
||||||
|
} else {
|
||||||
|
this.typesWithAllActions.delete(type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (newValue: string[]) => {}
|
||||||
|
|
||||||
|
onTouched = () => {}
|
||||||
|
|
||||||
|
disabled: boolean = false
|
||||||
|
|
||||||
|
registerOnChange(fn: any): void {
|
||||||
|
this.onChange = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: any): void {
|
||||||
|
this.onTouched = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState?(isDisabled: boolean): void {
|
||||||
|
this.disabled = isDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.form.valueChanges.subscribe((newValue) => {
|
||||||
|
let permissions = []
|
||||||
|
Object.entries(newValue).forEach(([typeKey, typeValue]) => {
|
||||||
|
// e.g. [Document, { Add: true, View: true ... }]
|
||||||
|
const selectedActions = Object.entries(typeValue).filter(
|
||||||
|
([actionKey, actionValue]) => actionValue == true
|
||||||
|
)
|
||||||
|
|
||||||
|
selectedActions.forEach(([actionKey, actionValue]) => {
|
||||||
|
permissions.push(
|
||||||
|
(PermissionType[typeKey] as string).replace(
|
||||||
|
'%s',
|
||||||
|
PermissionAction[actionKey]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (selectedActions.length == Object.entries(typeValue).length) {
|
||||||
|
this.typesWithAllActions.add(typeKey)
|
||||||
|
} else {
|
||||||
|
this.typesWithAllActions.delete(typeKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.onChange(permissions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAll(event, type) {
|
||||||
|
const typeGroup = this.form.get(type)
|
||||||
|
if (event.target.checked) {
|
||||||
|
Object.keys(PermissionAction).forEach((action) => {
|
||||||
|
typeGroup.get(action).patchValue(true)
|
||||||
|
})
|
||||||
|
this.typesWithAllActions.add(type)
|
||||||
|
} else {
|
||||||
|
Object.keys(PermissionAction).forEach((action) => {
|
||||||
|
typeGroup.get(action).patchValue(false)
|
||||||
|
})
|
||||||
|
this.typesWithAllActions.delete(type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isInherited(typeKey: string, actionKey: string = null) {
|
||||||
|
if (this._inheritedPermissions.length == 0) return false
|
||||||
|
else if (actionKey) {
|
||||||
|
return this._inheritedPermissions.includes(
|
||||||
|
this.permissionsService.getPermissionCode(
|
||||||
|
PermissionAction[actionKey],
|
||||||
|
PermissionType[typeKey]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return Object.values(PermissionAction).every((action) => {
|
||||||
|
return this._inheritedPermissions.includes(
|
||||||
|
this.permissionsService.getPermissionCode(
|
||||||
|
action as PermissionAction,
|
||||||
|
PermissionType[typeKey]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if checkbox is disabled either because "All", inhereted or entire component disabled
|
||||||
|
isDisabled(typeKey: string, actionKey: string) {
|
||||||
|
return this.typesWithAllActions.has(typeKey) ||
|
||||||
|
this.isInherited(typeKey, actionKey) ||
|
||||||
|
this.disabled
|
||||||
|
? true
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
}
|
@ -28,12 +28,14 @@
|
|||||||
|
|
||||||
<app-welcome-widget *ngIf="settingsService.offerTour()" tourAnchor="tour.dashboard"></app-welcome-widget>
|
<app-welcome-widget *ngIf="settingsService.offerTour()" tourAnchor="tour.dashboard"></app-welcome-widget>
|
||||||
|
|
||||||
<ng-container *ngFor="let v of savedViewService.dashboardViews; first as isFirst">
|
<div *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||||
<app-saved-view-widget *ngIf="isFirst; else noTour" [savedView]="v" tourAnchor="tour.dashboard"></app-saved-view-widget>
|
<ng-container *ngFor="let v of savedViewService.dashboardViews; first as isFirst">
|
||||||
<ng-template #noTour>
|
<app-saved-view-widget *ngIf="isFirst; else noTour" [savedView]="v" tourAnchor="tour.dashboard"></app-saved-view-widget>
|
||||||
<app-saved-view-widget [savedView]="v"></app-saved-view-widget>
|
<ng-template #noTour>
|
||||||
</ng-template>
|
<app-saved-view-widget [savedView]="v"></app-saved-view-widget>
|
||||||
</ng-container>
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
|
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
templateUrl: './dashboard.component.html',
|
templateUrl: './dashboard.component.html',
|
||||||
styleUrls: ['./dashboard.component.scss'],
|
styleUrls: ['./dashboard.component.scss'],
|
||||||
})
|
})
|
||||||
export class DashboardComponent {
|
export class DashboardComponent extends ComponentWithPermissions {
|
||||||
constructor(
|
constructor(
|
||||||
public savedViewService: SavedViewService,
|
public savedViewService: SavedViewService,
|
||||||
public settingsService: SettingsService
|
public settingsService: SettingsService
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
get subtitle() {
|
get subtitle() {
|
||||||
if (this.settingsService.displayName) {
|
if (this.settingsService.displayName) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<app-widget-frame [title]="savedView.name" [loading]="loading">
|
<app-widget-frame [title]="savedView.name" [loading]="loading">
|
||||||
|
|
||||||
<a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
|
<a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" i18n>Show all</a>
|
||||||
|
|
||||||
|
|
||||||
<table content class="table table-sm table-hover table-borderless mb-0">
|
<table content class="table table-sm table-hover table-borderless mb-0">
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<th scope="col" i18n>Title</th>
|
<th scope="col" i18n>Title</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||||
<tr *ngFor="let doc of documents">
|
<tr *ngFor="let doc of documents">
|
||||||
<td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.created_date | customDate}}</a></td>
|
<td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.created_date | customDate}}</a></td>
|
||||||
<td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></app-tag></a></td>
|
<td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></app-tag></a></td>
|
||||||
|
@ -9,13 +9,17 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'
|
|||||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
||||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
|
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-saved-view-widget',
|
selector: 'app-saved-view-widget',
|
||||||
templateUrl: './saved-view-widget.component.html',
|
templateUrl: './saved-view-widget.component.html',
|
||||||
styleUrls: ['./saved-view-widget.component.scss'],
|
styleUrls: ['./saved-view-widget.component.scss'],
|
||||||
})
|
})
|
||||||
export class SavedViewWidgetComponent implements OnInit, OnDestroy {
|
export class SavedViewWidgetComponent
|
||||||
|
extends ComponentWithPermissions
|
||||||
|
implements OnInit, OnDestroy
|
||||||
|
{
|
||||||
loading: boolean = true
|
loading: boolean = true
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -24,7 +28,9 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
|
|||||||
private list: DocumentListViewService,
|
private list: DocumentListViewService,
|
||||||
private consumerStatusService: ConsumerStatusService,
|
private consumerStatusService: ConsumerStatusService,
|
||||||
public openDocumentsService: OpenDocumentsService
|
public openDocumentsService: OpenDocumentsService
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
savedView: PaperlessSavedView
|
savedView: PaperlessSavedView
|
||||||
@ -74,6 +80,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
clickTag(tag: PaperlessTag, event: MouseEvent) {
|
clickTag(tag: PaperlessTag, event: MouseEvent) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
event.stopImmediatePropagation()
|
||||||
|
|
||||||
this.list.quickFilter([
|
this.list.quickFilter([
|
||||||
{ rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() },
|
{ rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() },
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div content tourAnchor="tour.upload-widget">
|
<div content tourAnchor="tour.upload-widget">
|
||||||
<form>
|
<form *appIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Document }">
|
||||||
<ngx-file-drop dropZoneLabel="Drop documents here or" browseBtnLabel="Browse files" (onFileDrop)="dropped($event)"
|
<ngx-file-drop dropZoneLabel="Drop documents here or" browseBtnLabel="Browse files" (onFileDrop)="dropped($event)"
|
||||||
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)" dropZoneClassName="bg-light card"
|
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)" dropZoneClassName="bg-light card"
|
||||||
multiple="true" contentClassName="justify-content-center d-flex align-items-center py-5 px-2" [showBrowseBtn]=true
|
multiple="true" contentClassName="justify-content-center d-flex align-items-center py-5 px-2" [showBrowseBtn]=true
|
||||||
@ -40,13 +40,15 @@
|
|||||||
<h6 class="alert-heading">{{status.filename}}</h6>
|
<h6 class="alert-heading">{{status.filename}}</h6>
|
||||||
<p class="mb-0 pb-1" *ngIf="!isFinished(status) || (isFinished(status) && !status.documentId)">{{status.message}}</p>
|
<p class="mb-0 pb-1" *ngIf="!isFinished(status) || (isFinished(status) && !status.documentId)">{{status.message}}</p>
|
||||||
<ngb-progressbar [value]="status.getProgress()" [max]="1" [type]="getStatusColor(status)"></ngb-progressbar>
|
<ngb-progressbar [value]="status.getProgress()" [max]="1" [type]="getStatusColor(status)"></ngb-progressbar>
|
||||||
<div *ngIf="isFinished(status)">
|
<div *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||||
<button *ngIf="status.documentId" class="btn btn-sm btn-outline-primary btn-open" routerLink="/documents/{{status.documentId}}" (click)="dismiss(status)">
|
<div *ngIf="isFinished(status)">
|
||||||
<small i18n>Open document</small>
|
<button *ngIf="status.documentId" class="btn btn-sm btn-outline-primary btn-open" routerLink="/documents/{{status.documentId}}" (click)="dismiss(status)">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
|
<small i18n>Open document</small>
|
||||||
<path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
|
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
|
||||||
</svg>
|
<path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
|
||||||
</button>
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ngb-alert>
|
</ngb-alert>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgxFileDropEntry } from 'ngx-file-drop'
|
import { NgxFileDropEntry } from 'ngx-file-drop'
|
||||||
|
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
|
||||||
import {
|
import {
|
||||||
ConsumerStatusService,
|
ConsumerStatusService,
|
||||||
FileStatus,
|
FileStatus,
|
||||||
@ -14,13 +15,15 @@ const MAX_ALERTS = 5
|
|||||||
templateUrl: './upload-file-widget.component.html',
|
templateUrl: './upload-file-widget.component.html',
|
||||||
styleUrls: ['./upload-file-widget.component.scss'],
|
styleUrls: ['./upload-file-widget.component.scss'],
|
||||||
})
|
})
|
||||||
export class UploadFileWidgetComponent {
|
export class UploadFileWidgetComponent extends ComponentWithPermissions {
|
||||||
alertsExpanded = false
|
alertsExpanded = false
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private consumerStatusService: ConsumerStatusService,
|
private consumerStatusService: ConsumerStatusService,
|
||||||
private uploadDocumentsService: UploadDocumentsService
|
private uploadDocumentsService: UploadDocumentsService
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
getStatus() {
|
getStatus() {
|
||||||
return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS)
|
return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div *ngIf="comments">
|
<div *ngIf="comments">
|
||||||
<form [formGroup]="commentForm" class="needs-validation mt-3" novalidate>
|
<form [formGroup]="commentForm" class="needs-validation mt-3" *appIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Comment }" novalidate>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<textarea class="form-control form-control-sm" [class.is-invalid]="newCommentError" rows="3" formControlName="newComment" placeholder="Enter comment" i18n-placeholder required></textarea>
|
<textarea class="form-control form-control-sm" [class.is-invalid]="newCommentError" rows="3" formControlName="newComment" placeholder="Enter comment" i18n-placeholder required></textarea>
|
||||||
<div class="invalid-feedback" i18n>
|
<div class="invalid-feedback" i18n>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex card-footer small bg-light text-primary justify-content-between align-items-center">
|
<div class="d-flex card-footer small bg-light text-primary justify-content-between align-items-center">
|
||||||
<span>{{displayName(comment)}} - {{ comment.created | customDate}}</span>
|
<span>{{displayName(comment)}} - {{ comment.created | customDate}}</span>
|
||||||
<button type="button" class="btn btn-link btn-sm p-0 fade" (click)="deleteComment(comment.id)">
|
<button type="button" class="btn btn-link btn-sm p-0 fade" (click)="deleteComment(comment.id)" *appIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Comment }">
|
||||||
<svg width="13" height="13" fill="currentColor">
|
<svg width="13" height="13" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -4,13 +4,14 @@ import { PaperlessDocumentComment } from 'src/app/data/paperless-document-commen
|
|||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
import { first } from 'rxjs/operators'
|
import { first } from 'rxjs/operators'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
|
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-document-comments',
|
selector: 'app-document-comments',
|
||||||
templateUrl: './document-comments.component.html',
|
templateUrl: './document-comments.component.html',
|
||||||
styleUrls: ['./document-comments.component.scss'],
|
styleUrls: ['./document-comments.component.scss'],
|
||||||
})
|
})
|
||||||
export class DocumentCommentsComponent {
|
export class DocumentCommentsComponent extends ComponentWithPermissions {
|
||||||
commentForm: FormGroup = new FormGroup({
|
commentForm: FormGroup = new FormGroup({
|
||||||
newComment: new FormControl(''),
|
newComment: new FormControl(''),
|
||||||
})
|
})
|
||||||
@ -32,7 +33,9 @@ export class DocumentCommentsComponent {
|
|||||||
constructor(
|
constructor(
|
||||||
private commentsService: DocumentCommentsService,
|
private commentsService: DocumentCommentsService,
|
||||||
private toastService: ToastService
|
private toastService: ToastService
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
update(): void {
|
update(): void {
|
||||||
this.networkActive = true
|
this.networkActive = true
|
||||||
@ -89,8 +92,8 @@ export class DocumentCommentsComponent {
|
|||||||
displayName(comment: PaperlessDocumentComment): string {
|
displayName(comment: PaperlessDocumentComment): string {
|
||||||
if (!comment.user) return ''
|
if (!comment.user) return ''
|
||||||
let nameComponents = []
|
let nameComponents = []
|
||||||
if (comment.user.firstname) nameComponents.unshift(comment.user.firstname)
|
if (comment.user.first_name) nameComponents.unshift(comment.user.first_name)
|
||||||
if (comment.user.lastname) nameComponents.unshift(comment.user.lastname)
|
if (comment.user.last_name) nameComponents.unshift(comment.user.last_name)
|
||||||
if (comment.user.username) {
|
if (comment.user.username) {
|
||||||
if (nameComponents.length > 0)
|
if (nameComponents.length > 0)
|
||||||
nameComponents.push(`(${comment.user.username})`)
|
nameComponents.push(`(${comment.user.username})`)
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
|
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()">
|
<button type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()" [disabled]="!userIsOwner">
|
||||||
<svg class="buttonicon" fill="currentColor">
|
<svg class="buttonicon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||||
</svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
|
</svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="btn-group" ngbDropdown role="group" *ngIf="metadata?.has_archive_version">
|
<div class="btn-group" ngbDropdown role="group" *ngIf="metadata?.has_archive_version">
|
||||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle-split" ngbDropdownToggle></button>
|
<button class="btn btn-sm btn-outline-primary dropdown-toggle" ngbDropdownToggle></button>
|
||||||
<div class="dropdown-menu shadow" ngbDropdownMenu>
|
<div class="dropdown-menu shadow" ngbDropdownMenu>
|
||||||
<a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a>
|
<a ngbDropdownItem [href]="downloadOriginalUrl" i18n>Download original</a>
|
||||||
</div>
|
</div>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()">
|
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="redoOcr()" [disabled]="!userCanEdit">
|
||||||
<svg class="buttonicon" fill="currentColor">
|
<svg class="buttonicon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
|
<use xlink:href="assets/bootstrap-icons.svg#arrow-counterclockwise" />
|
||||||
</svg><span class="d-none d-lg-inline ps-1" i18n>Redo OCR</span>
|
</svg><span class="d-none d-lg-inline ps-1" i18n>Redo OCR</span>
|
||||||
@ -170,19 +170,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li [ngbNavItem]="5" *ngIf="commentsEnabled">
|
<li [ngbNavItem]="5" *ngIf="commentsEnabled">
|
||||||
<a ngbNavLink i18n>Comments</a>
|
<a ngbNavLink i18n>Comments</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<app-document-comments [documentId]="documentId"></app-document-comments>
|
<app-document-comments [documentId]="documentId"></app-document-comments>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li [ngbNavItem]="6" *appIfOwner="document">
|
||||||
|
<a ngbNavLink i18n>Permissions</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
|
<div class="mb-3">
|
||||||
|
<app-permissions-form [users]="users" formControlName="permissions_form"></app-permissions-form>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || (isDirty$ | async) !== true">Discard</button>
|
<ng-container>
|
||||||
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || (isDirty$ | async) !== true || error">Save & next</button>
|
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true">Discard</button>
|
||||||
<button type="submit" class="btn btn-primary" i18n [disabled]="networkActive || (isDirty$ | async) !== true || error">Save</button>
|
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true || error">Save & next</button>
|
||||||
|
<button type="submit" class="btn btn-primary" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) !== true || error">Save</button>
|
||||||
|
</ng-container>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -35,6 +35,13 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
|||||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||||
import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||||
|
import {
|
||||||
|
PermissionAction,
|
||||||
|
PermissionsService,
|
||||||
|
PermissionType,
|
||||||
|
} from 'src/app/services/permissions.service'
|
||||||
|
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-document-detail',
|
selector: 'app-document-detail',
|
||||||
@ -58,6 +65,7 @@ export class DocumentDetailComponent
|
|||||||
document: PaperlessDocument
|
document: PaperlessDocument
|
||||||
metadata: PaperlessDocumentMetadata
|
metadata: PaperlessDocumentMetadata
|
||||||
suggestions: PaperlessDocumentSuggestions
|
suggestions: PaperlessDocumentSuggestions
|
||||||
|
users: PaperlessUser[]
|
||||||
|
|
||||||
title: string
|
title: string
|
||||||
titleSubject: Subject<string> = new Subject()
|
titleSubject: Subject<string> = new Subject()
|
||||||
@ -78,6 +86,7 @@ export class DocumentDetailComponent
|
|||||||
storage_path: new FormControl(),
|
storage_path: new FormControl(),
|
||||||
archive_serial_number: new FormControl(),
|
archive_serial_number: new FormControl(),
|
||||||
tags: new FormControl([]),
|
tags: new FormControl([]),
|
||||||
|
permissions_form: new FormControl(null),
|
||||||
})
|
})
|
||||||
|
|
||||||
previewCurrentPage: number = 1
|
previewCurrentPage: number = 1
|
||||||
@ -106,6 +115,9 @@ export class DocumentDetailComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PermissionAction = PermissionAction
|
||||||
|
PermissionType = PermissionType
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private documentsService: DocumentService,
|
private documentsService: DocumentService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@ -118,7 +130,9 @@ export class DocumentDetailComponent
|
|||||||
private documentTitlePipe: DocumentTitlePipe,
|
private documentTitlePipe: DocumentTitlePipe,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private settings: SettingsService,
|
private settings: SettingsService,
|
||||||
private storagePathService: StoragePathService
|
private storagePathService: StoragePathService,
|
||||||
|
private permissionsService: PermissionsService,
|
||||||
|
private userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
titleKeyUp(event) {
|
titleKeyUp(event) {
|
||||||
@ -147,7 +161,13 @@ export class DocumentDetailComponent
|
|||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.error = null
|
this.error = null
|
||||||
Object.assign(this.document, this.documentForm.value)
|
const docValues = Object.assign({}, this.documentForm.value)
|
||||||
|
docValues['owner'] =
|
||||||
|
this.documentForm.get('permissions_form').value['owner']
|
||||||
|
docValues['set_permissions'] =
|
||||||
|
this.documentForm.get('permissions_form').value['set_permissions']
|
||||||
|
delete docValues['permissions_form']
|
||||||
|
Object.assign(this.document, docValues)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.correspondentService
|
this.correspondentService
|
||||||
@ -165,6 +185,11 @@ export class DocumentDetailComponent
|
|||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe((result) => (this.storagePaths = result.results))
|
.subscribe((result) => (this.storagePaths = result.results))
|
||||||
|
|
||||||
|
this.userService
|
||||||
|
.listAll()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((result) => (this.users = result.results))
|
||||||
|
|
||||||
this.route.paramMap
|
this.route.paramMap
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(this.unsubscribeNotifier),
|
takeUntil(this.unsubscribeNotifier),
|
||||||
@ -232,6 +257,10 @@ export class DocumentDetailComponent
|
|||||||
storage_path: doc.storage_path,
|
storage_path: doc.storage_path,
|
||||||
archive_serial_number: doc.archive_serial_number,
|
archive_serial_number: doc.archive_serial_number,
|
||||||
tags: [...doc.tags],
|
tags: [...doc.tags],
|
||||||
|
permissions_form: {
|
||||||
|
owner: doc.owner,
|
||||||
|
set_permissions: doc.permissions,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
this.isDirty$ = dirtyCheck(
|
this.isDirty$ = dirtyCheck(
|
||||||
@ -286,7 +315,14 @@ export class DocumentDetailComponent
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
this.title = this.documentTitlePipe.transform(doc.title)
|
this.title = this.documentTitlePipe.transform(doc.title)
|
||||||
this.documentForm.patchValue(doc)
|
const docFormValues = Object.assign({}, doc)
|
||||||
|
docFormValues['permissions_form'] = {
|
||||||
|
owner: doc.owner,
|
||||||
|
set_permissions: doc.permissions,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.documentForm.patchValue(docFormValues, { emitEvent: false })
|
||||||
|
if (!this.userCanEdit) this.documentForm.disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
createDocumentType(newName: string) {
|
createDocumentType(newName: string) {
|
||||||
@ -378,7 +414,7 @@ export class DocumentDetailComponent
|
|||||||
.update(this.document)
|
.update(this.document)
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (result) => {
|
next: () => {
|
||||||
this.close()
|
this.close()
|
||||||
this.networkActive = false
|
this.networkActive = false
|
||||||
this.error = null
|
this.error = null
|
||||||
@ -564,6 +600,36 @@ export class DocumentDetailComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
get commentsEnabled(): boolean {
|
get commentsEnabled(): boolean {
|
||||||
return this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED)
|
return (
|
||||||
|
this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) &&
|
||||||
|
this.permissionsService.currentUserCan(
|
||||||
|
PermissionAction.View,
|
||||||
|
PermissionType.Document
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
get userIsOwner(): boolean {
|
||||||
|
let doc: PaperlessDocument = Object.assign({}, this.document)
|
||||||
|
// dont disable while editing
|
||||||
|
if (this.document && this.store?.value.owner) {
|
||||||
|
doc.owner = this.store?.value.owner
|
||||||
|
}
|
||||||
|
return !this.document || this.permissionsService.currentUserOwnsObject(doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
get userCanEdit(): boolean {
|
||||||
|
let doc: PaperlessDocument = Object.assign({}, this.document)
|
||||||
|
// dont disable while editing
|
||||||
|
if (this.document && this.store?.value.owner) {
|
||||||
|
doc.owner = this.store?.value.owner
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
!this.document ||
|
||||||
|
this.permissionsService.currentUserHasObjectPermissions(
|
||||||
|
PermissionAction.Change,
|
||||||
|
doc
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-100 d-xl-none"></div>
|
<div class="w-100 d-xl-none"></div>
|
||||||
<div class="col-auto mb-2 mb-xl-0">
|
<div class="col-auto mb-2 mb-xl-0">
|
||||||
<div class="d-flex">
|
<div class="d-flex" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||||
<label class="ms-auto mt-1 mb-0 me-2" i18n>Edit:</label>
|
<label class="ms-auto mt-1 mb-0 me-2" i18n>Edit:</label>
|
||||||
<app-filterable-dropdown class="me-2 me-md-3" title="Tags" icon="tag-fill" i18n-title
|
<app-filterable-dropdown class="me-2 me-md-3" title="Tags" icon="tag-fill" i18n-title
|
||||||
filterPlaceholder="Filter tags" i18n-filterPlaceholder
|
filterPlaceholder="Filter tags" i18n-filterPlaceholder
|
||||||
[items]="tags"
|
[items]="tags"
|
||||||
|
[disabled]="!userCanEditAll"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
@ -38,6 +39,7 @@
|
|||||||
<app-filterable-dropdown class="me-2 me-md-3" title="Correspondent" icon="person-fill" i18n-title
|
<app-filterable-dropdown class="me-2 me-md-3" title="Correspondent" icon="person-fill" i18n-title
|
||||||
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
|
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
|
||||||
[items]="correspondents"
|
[items]="correspondents"
|
||||||
|
[disabled]="!userCanEditAll"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
(opened)="openCorrespondentDropdown()"
|
(opened)="openCorrespondentDropdown()"
|
||||||
@ -47,6 +49,7 @@
|
|||||||
<app-filterable-dropdown class="me-2 me-md-3" title="Document type" icon="file-earmark-fill" i18n-title
|
<app-filterable-dropdown class="me-2 me-md-3" title="Document type" icon="file-earmark-fill" i18n-title
|
||||||
filterPlaceholder="Filter document types" i18n-filterPlaceholder
|
filterPlaceholder="Filter document types" i18n-filterPlaceholder
|
||||||
[items]="documentTypes"
|
[items]="documentTypes"
|
||||||
|
[disabled]="!userCanEditAll"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
(opened)="openDocumentTypeDropdown()"
|
(opened)="openDocumentTypeDropdown()"
|
||||||
@ -56,6 +59,7 @@
|
|||||||
<app-filterable-dropdown class="me-2 me-md-3" title="Storage path" icon="folder-fill" i18n-title
|
<app-filterable-dropdown class="me-2 me-md-3" title="Storage path" icon="folder-fill" i18n-title
|
||||||
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
||||||
[items]="storagePaths"
|
[items]="storagePaths"
|
||||||
|
[disabled]="!userCanEditAll"
|
||||||
[editing]="true"
|
[editing]="true"
|
||||||
[applyOnClose]="applyOnClose"
|
[applyOnClose]="applyOnClose"
|
||||||
(opened)="openStoragePathDropdown()"
|
(opened)="openStoragePathDropdown()"
|
||||||
@ -65,7 +69,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto ms-auto mb-2 mb-xl-0 d-flex">
|
<div class="col-auto ms-auto mb-2 mb-xl-0 d-flex">
|
||||||
<div class="btn-group btn-group-sm me-2">
|
<div class="btn-toolbar me-2">
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="setPermissions()" [disabled]="!userOwnsAll">
|
||||||
|
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#person-fill-lock" />
|
||||||
|
</svg> <ng-container i18n>Permissions</ng-container>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div ngbDropdown class="me-2 d-flex">
|
<div ngbDropdown class="me-2 d-flex">
|
||||||
<button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle>
|
<button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle>
|
||||||
<svg class="toolbaricon" fill="currentColor">
|
<svg class="toolbaricon" fill="currentColor">
|
||||||
@ -74,7 +85,7 @@
|
|||||||
<div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div>
|
<div class="d-none d-sm-inline"> <ng-container i18n>Actions</ng-container></div>
|
||||||
</button>
|
</button>
|
||||||
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
|
<div ngbDropdownMenu aria-labelledby="dropdownSelect" class="shadow">
|
||||||
<button ngbDropdownItem (click)="redoOcrSelected()" i18n>Redo OCR</button>
|
<button ngbDropdownItem (click)="redoOcrSelected()" [disabled]="!userCanEditAll" i18n>Redo OCR</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -120,7 +131,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group btn-group-sm me-2">
|
<div class="btn-group btn-group-sm me-2">
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()">
|
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *appIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }" [disabled]="!userOwnsAll">
|
||||||
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
<use xlink:href="assets/bootstrap-icons.svg#trash" />
|
||||||
</svg> <ng-container i18n>Delete</ng-container>
|
</svg> <ng-container i18n>Delete</ng-container>
|
||||||
|
@ -25,6 +25,9 @@ import { saveAs } from 'file-saver'
|
|||||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||||
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
|
||||||
|
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
import { first, Subject, takeUntil } from 'rxjs'
|
import { first, Subject, takeUntil } from 'rxjs'
|
||||||
|
|
||||||
@ -33,7 +36,10 @@ import { first, Subject, takeUntil } from 'rxjs'
|
|||||||
templateUrl: './bulk-editor.component.html',
|
templateUrl: './bulk-editor.component.html',
|
||||||
styleUrls: ['./bulk-editor.component.scss'],
|
styleUrls: ['./bulk-editor.component.scss'],
|
||||||
})
|
})
|
||||||
export class BulkEditorComponent implements OnInit, OnDestroy {
|
export class BulkEditorComponent
|
||||||
|
extends ComponentWithPermissions
|
||||||
|
implements OnInit, OnDestroy
|
||||||
|
{
|
||||||
tags: PaperlessTag[]
|
tags: PaperlessTag[]
|
||||||
correspondents: PaperlessCorrespondent[]
|
correspondents: PaperlessCorrespondent[]
|
||||||
documentTypes: PaperlessDocumentType[]
|
documentTypes: PaperlessDocumentType[]
|
||||||
@ -63,8 +69,11 @@ export class BulkEditorComponent implements OnInit, OnDestroy {
|
|||||||
private openDocumentService: OpenDocumentsService,
|
private openDocumentService: OpenDocumentsService,
|
||||||
private settings: SettingsService,
|
private settings: SettingsService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private storagePathService: StoragePathService
|
private storagePathService: StoragePathService,
|
||||||
) {}
|
private permissionService: PermissionsService
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
applyOnClose: boolean = this.settings.get(
|
applyOnClose: boolean = this.settings.get(
|
||||||
SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE
|
SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE
|
||||||
@ -73,6 +82,25 @@ export class BulkEditorComponent implements OnInit, OnDestroy {
|
|||||||
SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS
|
SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
get userCanEditAll(): boolean {
|
||||||
|
let canEdit: boolean = true
|
||||||
|
const docs = this.list.documents.filter((d) => this.list.selected.has(d.id))
|
||||||
|
canEdit = docs.every((d) =>
|
||||||
|
this.permissionService.currentUserHasObjectPermissions(
|
||||||
|
this.PermissionAction.Change,
|
||||||
|
d
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return canEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
get userOwnsAll(): boolean {
|
||||||
|
let ownsAll: boolean = true
|
||||||
|
const docs = this.list.documents.filter((d) => this.list.selected.has(d.id))
|
||||||
|
ownsAll = docs.every((d) => this.permissionService.currentUserOwnsObject(d))
|
||||||
|
return ownsAll
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.tagService
|
this.tagService
|
||||||
.listAll()
|
.listAll()
|
||||||
@ -463,4 +491,14 @@ export class BulkEditorComponent implements OnInit, OnDestroy {
|
|||||||
this.executeBulkOperation(modal, 'redo_ocr', {})
|
this.executeBulkOperation(modal, 'redo_ocr', {})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPermissions() {
|
||||||
|
let modal = this.modalService.open(PermissionsDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.confirmClicked.subscribe((permissions) => {
|
||||||
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
this.executeBulkOperation(modal, 'set_permissions', permissions)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
<use xlink:href="assets/bootstrap-icons.svg#diagram-3"/>
|
<use xlink:href="assets/bootstrap-icons.svg#diagram-3"/>
|
||||||
</svg> <span class="d-none d-md-inline" i18n>More like this</span>
|
</svg> <span class="d-none d-md-inline" i18n>More like this</span>
|
||||||
</a>
|
</a>
|
||||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary">
|
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
|
||||||
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#pencil"/>
|
<use xlink:href="assets/bootstrap-icons.svg#pencil"/>
|
||||||
</svg> <span class="d-none d-md-inline" i18n>Edit</span>
|
</svg> <span class="d-none d-md-inline" i18n>Edit</span>
|
||||||
|
@ -10,6 +10,7 @@ import { DocumentService } from 'src/app/services/rest/document.service'
|
|||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||||
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-document-card-large',
|
selector: 'app-document-card-large',
|
||||||
@ -19,11 +20,13 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
|||||||
'../popover-preview/popover-preview.scss',
|
'../popover-preview/popover-preview.scss',
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DocumentCardLargeComponent {
|
export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
||||||
constructor(
|
constructor(
|
||||||
private documentService: DocumentService,
|
private documentService: DocumentService,
|
||||||
private settingsService: SettingsService
|
private settingsService: SettingsService
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
selected = false
|
selected = false
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<div class="btn-group w-100">
|
<div class="btn-group w-100">
|
||||||
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" title="Edit" i18n-title>
|
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary" title="Edit" i18n-title *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n-title>
|
||||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
|
<path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -11,6 +11,7 @@ import { DocumentService } from 'src/app/services/rest/document.service'
|
|||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||||
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-document-card-small',
|
selector: 'app-document-card-small',
|
||||||
@ -20,11 +21,13 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
|||||||
'../popover-preview/popover-preview.scss',
|
'../popover-preview/popover-preview.scss',
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DocumentCardSmallComponent {
|
export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
||||||
constructor(
|
constructor(
|
||||||
private documentService: DocumentService,
|
private documentService: DocumentService,
|
||||||
private settingsService: SettingsService
|
private settingsService: SettingsService
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
selected = false
|
selected = false
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn-group ms-2 flex-fill" ngbDropdown role="group">
|
<div class="btn-group ms-2 flex-fill" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }" ngbDropdown role="group">
|
||||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle flex-fill" tourAnchor="tour.documents-views" ngbDropdownToggle>
|
<button class="btn btn-sm btn-outline-primary dropdown-toggle flex-fill" tourAnchor="tour.documents-views" ngbDropdownToggle>
|
||||||
<ng-container i18n>Views</ng-container>
|
<ng-container i18n>Views</ng-container>
|
||||||
<div *ngIf="savedViewIsModified" class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
|
<div *ngIf="savedViewIsModified" class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
|
||||||
@ -72,8 +72,10 @@
|
|||||||
<div class="dropdown-divider" *ngIf="savedViewService.allViews.length > 0"></div>
|
<div class="dropdown-divider" *ngIf="savedViewService.allViews.length > 0"></div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<button ngbDropdownItem (click)="saveViewConfig()" *ngIf="list.activeSavedViewId" [disabled]="!savedViewIsModified" i18n>Save "{{list.activeSavedViewTitle}}"</button>
|
<div *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.SavedView }">
|
||||||
<button ngbDropdownItem (click)="saveViewConfigAs()" i18n>Save as...</button>
|
<button ngbDropdownItem (click)="saveViewConfig()" *ngIf="list.activeSavedViewId" [disabled]="!savedViewIsModified" i18n>Save "{{list.activeSavedViewTitle}}"</button>
|
||||||
|
</div>
|
||||||
|
<button ngbDropdownItem (click)="saveViewConfigAs()" *appIfPermissions="{ action: PermissionAction.Add, type: PermissionType.SavedView }" i18n>Save as...</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
} from 'src/app/services/rest/document.service'
|
} from 'src/app/services/rest/document.service'
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
|
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
|
||||||
import { FilterEditorComponent } from './filter-editor/filter-editor.component'
|
import { FilterEditorComponent } from './filter-editor/filter-editor.component'
|
||||||
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'
|
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'
|
||||||
|
|
||||||
@ -38,7 +39,10 @@ import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-vi
|
|||||||
templateUrl: './document-list.component.html',
|
templateUrl: './document-list.component.html',
|
||||||
styleUrls: ['./document-list.component.scss'],
|
styleUrls: ['./document-list.component.scss'],
|
||||||
})
|
})
|
||||||
export class DocumentListComponent implements OnInit, OnDestroy {
|
export class DocumentListComponent
|
||||||
|
extends ComponentWithPermissions
|
||||||
|
implements OnInit, OnDestroy
|
||||||
|
{
|
||||||
constructor(
|
constructor(
|
||||||
public list: DocumentListViewService,
|
public list: DocumentListViewService,
|
||||||
public savedViewService: SavedViewService,
|
public savedViewService: SavedViewService,
|
||||||
@ -48,7 +52,9 @@ export class DocumentListComponent implements OnInit, OnDestroy {
|
|||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private consumerStatusService: ConsumerStatusService,
|
private consumerStatusService: ConsumerStatusService,
|
||||||
public openDocumentsService: OpenDocumentsService
|
public openDocumentsService: OpenDocumentsService
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
@ViewChild('filterEditor')
|
@ViewChild('filterEditor')
|
||||||
private filterEditor: FilterEditorComponent
|
private filterEditor: FilterEditorComponent
|
||||||
|
@ -4,6 +4,10 @@ import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'
|
|||||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
|
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
|
||||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
|
import {
|
||||||
|
PermissionsService,
|
||||||
|
PermissionType,
|
||||||
|
} from 'src/app/services/permissions.service'
|
||||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||||
@ -21,6 +25,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Paperles
|
|||||||
modalService: NgbModal,
|
modalService: NgbModal,
|
||||||
toastService: ToastService,
|
toastService: ToastService,
|
||||||
documentListViewService: DocumentListViewService,
|
documentListViewService: DocumentListViewService,
|
||||||
|
permissionsService: PermissionsService,
|
||||||
private datePipe: CustomDatePipe
|
private datePipe: CustomDatePipe
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -29,9 +34,11 @@ export class CorrespondentListComponent extends ManagementListComponent<Paperles
|
|||||||
CorrespondentEditDialogComponent,
|
CorrespondentEditDialogComponent,
|
||||||
toastService,
|
toastService,
|
||||||
documentListViewService,
|
documentListViewService,
|
||||||
|
permissionsService,
|
||||||
FILTER_CORRESPONDENT,
|
FILTER_CORRESPONDENT,
|
||||||
$localize`correspondent`,
|
$localize`correspondent`,
|
||||||
$localize`correspondents`,
|
$localize`correspondents`,
|
||||||
|
PermissionType.Correspondent,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
key: 'last_correspondence',
|
key: 'last_correspondence',
|
||||||
|
@ -3,6 +3,10 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'
|
import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'
|
||||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
|
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
|
import {
|
||||||
|
PermissionsService,
|
||||||
|
PermissionType,
|
||||||
|
} from 'src/app/services/permissions.service'
|
||||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
||||||
@ -18,7 +22,8 @@ export class DocumentTypeListComponent extends ManagementListComponent<Paperless
|
|||||||
documentTypeService: DocumentTypeService,
|
documentTypeService: DocumentTypeService,
|
||||||
modalService: NgbModal,
|
modalService: NgbModal,
|
||||||
toastService: ToastService,
|
toastService: ToastService,
|
||||||
documentListViewService: DocumentListViewService
|
documentListViewService: DocumentListViewService,
|
||||||
|
permissionsService: PermissionsService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
documentTypeService,
|
documentTypeService,
|
||||||
@ -26,9 +31,11 @@ export class DocumentTypeListComponent extends ManagementListComponent<Paperless
|
|||||||
DocumentTypeEditDialogComponent,
|
DocumentTypeEditDialogComponent,
|
||||||
toastService,
|
toastService,
|
||||||
documentListViewService,
|
documentListViewService,
|
||||||
|
permissionsService,
|
||||||
FILTER_DOCUMENT_TYPE,
|
FILTER_DOCUMENT_TYPE,
|
||||||
$localize`document type`,
|
$localize`document type`,
|
||||||
$localize`document types`,
|
$localize`document types`,
|
||||||
|
PermissionType.DocumentType,
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<app-page-header title="{{ typeNamePlural | titlecase }}">
|
<app-page-header title="{{ typeNamePlural | titlecase }}">
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" i18n>Create</button>
|
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" *appIfPermissions="{ action: PermissionAction.Add, type: permissionType }" i18n>Create</button>
|
||||||
</app-page-header>
|
</app-page-header>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -41,24 +41,24 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
|
<div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
|
||||||
<button (click)="filterDocuments(object)" ngbDropdownItem i18n>Filter Documents</button>
|
<button (click)="filterDocuments(object)" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" ngbDropdownItem i18n>Filter Documents</button>
|
||||||
<button (click)="openEditDialog(object)" ngbDropdownItem i18n>Edit</button>
|
<button (click)="openEditDialog(object)" *appIfPermissions="{ action: PermissionAction.Change, type: permissionType }" ngbDropdownItem i18n>Edit</button>
|
||||||
<button class="text-danger" (click)="openDeleteDialog(object)" ngbDropdownItem i18n>Delete</button>
|
<button class="text-danger" (click)="openDeleteDialog(object)" *appIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" ngbDropdownItem i18n>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group d-none d-sm-block">
|
<div class="btn-group d-none d-sm-block">
|
||||||
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object)">
|
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object)" *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
|
||||||
<path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
|
<path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
|
||||||
</svg> <ng-container i18n>Documents</ng-container>
|
</svg> <ng-container i18n>Documents</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object)">
|
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object)" *appIfPermissions="{ action: PermissionAction.Change, type: permissionType }" [disabled]="!userCanEdit(object)">
|
||||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
|
<path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
|
||||||
</svg> <ng-container i18n>Edit</ng-container>
|
</svg> <ng-container i18n>Edit</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object)">
|
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object)" *appIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" [disabled]="!userCanDelete(object)">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
|
||||||
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
|
||||||
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>
|
||||||
|
@ -14,14 +14,20 @@ import {
|
|||||||
MATCH_AUTO,
|
MATCH_AUTO,
|
||||||
} from 'src/app/data/matching-model'
|
} from 'src/app/data/matching-model'
|
||||||
import { ObjectWithId } from 'src/app/data/object-with-id'
|
import { ObjectWithId } from 'src/app/data/object-with-id'
|
||||||
|
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
|
||||||
import {
|
import {
|
||||||
SortableDirective,
|
SortableDirective,
|
||||||
SortEvent,
|
SortEvent,
|
||||||
} from 'src/app/directives/sortable.directive'
|
} from 'src/app/directives/sortable.directive'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
|
import {
|
||||||
|
PermissionsService,
|
||||||
|
PermissionType,
|
||||||
|
} from 'src/app/services/permissions.service'
|
||||||
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
|
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
export interface ManagementListColumn {
|
export interface ManagementListColumn {
|
||||||
key: string
|
key: string
|
||||||
@ -35,6 +41,7 @@ export interface ManagementListColumn {
|
|||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class ManagementListComponent<T extends ObjectWithId>
|
export abstract class ManagementListComponent<T extends ObjectWithId>
|
||||||
|
extends ComponentWithPermissions
|
||||||
implements OnInit, OnDestroy
|
implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
@ -43,11 +50,15 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
|||||||
private editDialogComponent: any,
|
private editDialogComponent: any,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private documentListViewService: DocumentListViewService,
|
private documentListViewService: DocumentListViewService,
|
||||||
|
private permissionsService: PermissionsService,
|
||||||
protected filterRuleType: number,
|
protected filterRuleType: number,
|
||||||
public typeName: string,
|
public typeName: string,
|
||||||
public typeNamePlural: string,
|
public typeNamePlural: string,
|
||||||
|
public permissionType: PermissionType,
|
||||||
public extraColumns: ManagementListColumn[]
|
public extraColumns: ManagementListColumn[]
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
|
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
|
||||||
|
|
||||||
@ -209,4 +220,15 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
|
|||||||
onNameFilterKeyUp(event: KeyboardEvent) {
|
onNameFilterKeyUp(event: KeyboardEvent) {
|
||||||
if (event.code == 'Escape') this.nameFilterDebounce.next(null)
|
if (event.code == 'Escape') this.nameFilterDebounce.next(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userCanDelete(object: ObjectWithPermissions): boolean {
|
||||||
|
return this.permissionsService.currentUserOwnsObject(object)
|
||||||
|
}
|
||||||
|
|
||||||
|
userCanEdit(object: ObjectWithPermissions): boolean {
|
||||||
|
return this.permissionsService.currentUserHasObjectPermissions(
|
||||||
|
this.PermissionAction.Change,
|
||||||
|
object
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<app-page-header title="Settings" i18n-title>
|
<app-page-header title="Settings" i18n-title>
|
||||||
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()"><ng-container i18n>Start tour</ng-container></button>
|
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()"><ng-container i18n>Start tour</ng-container></button>
|
||||||
<a class="btn btn-sm btn-primary ms-3" href="admin/" target="_blank">
|
<a *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" class="btn btn-sm btn-primary ms-3" href="admin/" target="_blank">
|
||||||
<ng-container i18n>Open Django Admin</ng-container>
|
<ng-container i18n>Open Django Admin</ng-container>
|
||||||
<svg class="sidebaricon ms-1" fill="currentColor">
|
<svg class="sidebaricon ms-1" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#arrow-up-right"/>
|
<use xlink:href="assets/bootstrap-icons.svg#arrow-up-right"/>
|
||||||
@ -219,7 +219,7 @@
|
|||||||
|
|
||||||
<div class="mb-2 col-auto">
|
<div class="mb-2 col-auto">
|
||||||
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
|
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
|
||||||
<button type="button" class="btn btn-sm btn-outline-danger form-control" (click)="deleteSavedView(view)" i18n>Delete</button>
|
<button type="button" class="btn btn-sm btn-outline-danger form-control" (click)="deleteSavedView(view)" *appIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }" i18n>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -235,80 +235,84 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li [ngbNavItem]="SettingsNavIDs.Mail" (mouseover)="maybeInitializeTab(SettingsNavIDs.Mail)" (focusin)="maybeInitializeTab(SettingsNavIDs.Mail)">
|
<li *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailRule }" [ngbNavItem]="SettingsNavIDs.Mail" (mouseover)="maybeInitializeTab(SettingsNavIDs.Mail)" (focusin)="maybeInitializeTab(SettingsNavIDs.Mail)">
|
||||||
<a ngbNavLink i18n>Mail</a>
|
<a ngbNavLink i18n>Mail</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
|
|
||||||
<ng-container *ngIf="mailAccounts && mailRules">
|
<ng-container *ngIf="mailAccounts && mailRules">
|
||||||
<h4>
|
<ng-container *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }">
|
||||||
<ng-container i18n>Mail accounts</ng-container>
|
<h4>
|
||||||
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailAccount()">
|
<ng-container i18n>Mail accounts</ng-container>
|
||||||
<svg class="sidebaricon me-1" fill="currentColor">
|
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailAccount()">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
<svg class="sidebaricon me-1" fill="currentColor">
|
||||||
</svg>
|
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||||
<ng-container i18n>Add Account</ng-container>
|
</svg>
|
||||||
</button>
|
<ng-container i18n>Add Account</ng-container>
|
||||||
</h4>
|
</button>
|
||||||
<ul class="list-group" formGroupName="mailAccounts">
|
</h4>
|
||||||
|
<ul class="list-group" formGroupName="mailAccounts">
|
||||||
|
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col" i18n>Name</div>
|
<div class="col" i18n>Name</div>
|
||||||
<div class="col" i18n>Server</div>
|
<div class="col" i18n>Server</div>
|
||||||
<div class="col" i18n>Actions</div>
|
<div class="col" i18n>Actions</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li *ngFor="let account of mailAccounts" class="list-group-item" [formGroupName]="account.id">
|
<li *ngFor="let account of mailAccounts" class="list-group-item" [formGroupName]="account.id">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailAccount(account)">{{account.name}}</button></div>
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailAccount(account)">{{account.name}}</button></div>
|
||||||
<div class="col d-flex align-items-center">{{account.imap_server}}</div>
|
<div class="col d-flex align-items-center">{{account.imap_server}}</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-sm btn-primary" type="button" (click)="editMailAccount(account)" i18n>Edit</button>
|
<button *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" class="btn btn-sm btn-primary" type="button" (click)="editMailAccount(account)" i18n>Edit</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)" i18n>Delete</button>
|
<button *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailAccount }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailAccount(account)" i18n>Delete</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
|
||||||
|
|
||||||
<div *ngIf="mailAccounts.length === 0" i18n>No mail accounts defined.</div>
|
<div *ngIf="mailAccounts.length === 0" i18n>No mail accounts defined.</div>
|
||||||
</ul>
|
</ul>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<h4 class="mt-4">
|
<ng-container *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailRule }">
|
||||||
<ng-container i18n>Mail rules</ng-container>
|
<h4 class="mt-4">
|
||||||
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailRule()">
|
<ng-container i18n>Mail rules</ng-container>
|
||||||
<svg class="sidebaricon me-1" fill="currentColor">
|
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editMailRule()">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
<svg class="sidebaricon me-1" fill="currentColor">
|
||||||
</svg>
|
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||||
<ng-container i18n>Add Rule</ng-container>
|
</svg>
|
||||||
</button>
|
<ng-container i18n>Add Rule</ng-container>
|
||||||
</h4>
|
</button>
|
||||||
<ul class="list-group" formGroupName="mailRules">
|
</h4>
|
||||||
|
<ul class="list-group" formGroupName="mailRules">
|
||||||
|
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col" i18n>Name</div>
|
<div class="col" i18n>Name</div>
|
||||||
<div class="col" i18n>Account</div>
|
<div class="col" i18n>Account</div>
|
||||||
<div class="col" i18n>Actions</div>
|
<div class="col" i18n>Actions</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li *ngFor="let rule of mailRules" class="list-group-item" [formGroupName]="rule.id">
|
<li *ngFor="let rule of mailRules" class="list-group-item" [formGroupName]="rule.id">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailRule(rule)">{{rule.name}}</button></div>
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editMailRule(rule)">{{rule.name}}</button></div>
|
||||||
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
|
<div class="col d-flex align-items-center">{{(mailAccountService.getCached(rule.account) | async)?.name}}</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-sm btn-primary" type="button" (click)="editMailRule(rule)" i18n>Edit</button>
|
<button *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" class="btn btn-sm btn-primary" type="button" (click)="editMailRule(rule)" i18n>Edit</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)" i18n>Delete</button>
|
<button *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.MailRule }" class="btn btn-sm btn-outline-danger" type="button" (click)="deleteMailRule(rule)" i18n>Delete</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
|
||||||
|
|
||||||
<div *ngIf="mailRules.length === 0" i18n>No mail rules defined.</div>
|
<div *ngIf="mailRules.length === 0" i18n>No mail rules defined.</div>
|
||||||
</ul>
|
</ul>
|
||||||
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div *ngIf="!mailAccounts || !mailRules">
|
<div *ngIf="!mailAccounts || !mailRules">
|
||||||
@ -318,9 +322,95 @@
|
|||||||
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li [ngbNavItem]="SettingsNavIDs.UsersGroups" *appIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }" (mouseover)="maybeInitializeTab(SettingsNavIDs.UsersGroups)" (focusin)="maybeInitializeTab(SettingsNavIDs.UsersGroups)">
|
||||||
|
<a ngbNavLink i18n>Users & Groups</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
|
|
||||||
|
<ng-container *ngIf="users && groups">
|
||||||
|
<h4 class="d-flex">
|
||||||
|
<ng-container i18n>Users</ng-container>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editUser()">
|
||||||
|
<svg class="sidebaricon me-1" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||||
|
</svg>
|
||||||
|
<ng-container i18n>Add User</ng-container>
|
||||||
|
</button>
|
||||||
|
</h4>
|
||||||
|
<ul class="list-group" formGroupName="usersGroup">
|
||||||
|
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col" i18n>Username</div>
|
||||||
|
<div class="col" i18n>Name</div>
|
||||||
|
<div class="col" i18n>Groups</div>
|
||||||
|
<div class="col" i18n>Actions</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li *ngFor="let user of users" class="list-group-item" [formGroupName]="user.id">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editUser(user)">{{user.username}}</button></div>
|
||||||
|
<div class="col d-flex align-items-center">{{user.first_name}} {{user.last_name}}</div>
|
||||||
|
<div class="col d-flex align-items-center">{{user.groups?.map(getGroupName, this).join(', ')}}</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-sm btn-primary" type="button" (click)="editUser(user)" i18n>Edit</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteUser(user)" i18n>Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h4 class="mt-4 d-flex">
|
||||||
|
<ng-container i18n>Groups</ng-container>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary ms-4" (click)="editGroup()">
|
||||||
|
<svg class="sidebaricon me-1" fill="currentColor">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
|
||||||
|
</svg>
|
||||||
|
<ng-container i18n>Add Group</ng-container>
|
||||||
|
</button>
|
||||||
|
</h4>
|
||||||
|
<ul *ngIf="groups.length > 0" class="list-group" formGroupName="groupsGroup">
|
||||||
|
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col" i18n>Name</div>
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col" i18n>Actions</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li *ngFor="let group of groups" class="list-group-item" [formGroupName]="group.id">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editGroup(group)">{{group.name}}</button></div>
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-sm btn-primary" type="button" (click)="editGroup(group)" i18n>Edit</button>
|
||||||
|
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteGroup(group)" i18n>Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div *ngIf="groups.length === 0">No groups defined</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div *ngIf="!users || !groups">
|
||||||
|
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||||
|
<div class="visually-hidden" i18n>Loading...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-template>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
|
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
|
<button type="submit" class="btn btn-primary mb-2" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -29,15 +29,21 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
|||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { ViewportScroller } from '@angular/common'
|
import { ViewportScroller } from '@angular/common'
|
||||||
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
|
||||||
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
import { NgbModal, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
|
import { GroupService } from 'src/app/services/rest/group.service'
|
||||||
|
import { PaperlessUser } from 'src/app/data/paperless-user'
|
||||||
|
import { PaperlessGroup } from 'src/app/data/paperless-group'
|
||||||
|
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
||||||
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
|
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
|
||||||
import { PaperlessMailAccount } from 'src/app/data/paperless-mail-account'
|
import { PaperlessMailAccount } from 'src/app/data/paperless-mail-account'
|
||||||
import { PaperlessMailRule } from 'src/app/data/paperless-mail-rule'
|
import { PaperlessMailRule } from 'src/app/data/paperless-mail-rule'
|
||||||
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
import { MailAccountService } from 'src/app/services/rest/mail-account.service'
|
||||||
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
import { MailRuleService } from 'src/app/services/rest/mail-rule.service'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
|
||||||
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
|
||||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
|
||||||
import { NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
|
|
||||||
|
|
||||||
enum SettingsNavIDs {
|
enum SettingsNavIDs {
|
||||||
General = 1,
|
General = 1,
|
||||||
@ -53,12 +59,15 @@ enum SettingsNavIDs {
|
|||||||
styleUrls: ['./settings.component.scss'],
|
styleUrls: ['./settings.component.scss'],
|
||||||
})
|
})
|
||||||
export class SettingsComponent
|
export class SettingsComponent
|
||||||
|
extends ComponentWithPermissions
|
||||||
implements OnInit, AfterViewInit, OnDestroy, DirtyComponent
|
implements OnInit, AfterViewInit, OnDestroy, DirtyComponent
|
||||||
{
|
{
|
||||||
SettingsNavIDs = SettingsNavIDs
|
SettingsNavIDs = SettingsNavIDs
|
||||||
activeNavID: number
|
activeNavID: number
|
||||||
|
|
||||||
savedViewGroup = new FormGroup({})
|
savedViewGroup = new FormGroup({})
|
||||||
|
usersGroup = new FormGroup({})
|
||||||
|
groupsGroup = new FormGroup({})
|
||||||
|
|
||||||
mailAccountGroup = new FormGroup({})
|
mailAccountGroup = new FormGroup({})
|
||||||
mailRuleGroup = new FormGroup({})
|
mailRuleGroup = new FormGroup({})
|
||||||
@ -83,6 +92,8 @@ export class SettingsComponent
|
|||||||
notificationsConsumerSuccess: new FormControl(null),
|
notificationsConsumerSuccess: new FormControl(null),
|
||||||
notificationsConsumerFailed: new FormControl(null),
|
notificationsConsumerFailed: new FormControl(null),
|
||||||
notificationsConsumerSuppressOnDashboard: new FormControl(null),
|
notificationsConsumerSuppressOnDashboard: new FormControl(null),
|
||||||
|
usersGroup: this.usersGroup,
|
||||||
|
groupsGroup: this.groupsGroup,
|
||||||
|
|
||||||
savedViewsWarnOnUnsavedChange: new FormControl(null),
|
savedViewsWarnOnUnsavedChange: new FormControl(null),
|
||||||
savedViews: this.savedViewGroup,
|
savedViews: this.savedViewGroup,
|
||||||
@ -103,6 +114,9 @@ export class SettingsComponent
|
|||||||
unsubscribeNotifier: Subject<any> = new Subject()
|
unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
savePending: boolean = false
|
savePending: boolean = false
|
||||||
|
|
||||||
|
users: PaperlessUser[]
|
||||||
|
groups: PaperlessGroup[]
|
||||||
|
|
||||||
get computedDateLocale(): string {
|
get computedDateLocale(): string {
|
||||||
return (
|
return (
|
||||||
this.settingsForm.value.dateLocale ||
|
this.settingsForm.value.dateLocale ||
|
||||||
@ -121,10 +135,13 @@ export class SettingsComponent
|
|||||||
@Inject(LOCALE_ID) public currentLocale: string,
|
@Inject(LOCALE_ID) public currentLocale: string,
|
||||||
private viewportScroller: ViewportScroller,
|
private viewportScroller: ViewportScroller,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private router: Router,
|
|
||||||
public readonly tourService: TourService,
|
public readonly tourService: TourService,
|
||||||
|
private usersService: UserService,
|
||||||
|
private groupsService: GroupService,
|
||||||
|
private router: Router,
|
||||||
private modalService: NgbModal
|
private modalService: NgbModal
|
||||||
) {
|
) {
|
||||||
|
super()
|
||||||
this.settings.settingsSaved.subscribe(() => {
|
this.settings.settingsSaved.subscribe(() => {
|
||||||
if (!this.savePending) this.initialize()
|
if (!this.savePending) this.initialize()
|
||||||
})
|
})
|
||||||
@ -198,6 +215,8 @@ export class SettingsComponent
|
|||||||
savedViewsWarnOnUnsavedChange: this.settings.get(
|
savedViewsWarnOnUnsavedChange: this.settings.get(
|
||||||
SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE
|
SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE
|
||||||
),
|
),
|
||||||
|
usersGroup: {},
|
||||||
|
groupsGroup: {},
|
||||||
savedViews: {},
|
savedViews: {},
|
||||||
mailAccounts: {},
|
mailAccounts: {},
|
||||||
mailRules: {},
|
mailRules: {},
|
||||||
@ -229,6 +248,17 @@ export class SettingsComponent
|
|||||||
this.savedViews = r.results
|
this.savedViews = r.results
|
||||||
this.initialize(false)
|
this.initialize(false)
|
||||||
})
|
})
|
||||||
|
} else if (
|
||||||
|
navID == SettingsNavIDs.UsersGroups &&
|
||||||
|
(!this.users || !this.groups)
|
||||||
|
) {
|
||||||
|
this.usersService.listAll().subscribe((r) => {
|
||||||
|
this.users = r.results
|
||||||
|
this.groupsService.listAll().subscribe((r) => {
|
||||||
|
this.groups = r.results
|
||||||
|
this.initialize(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
} else if (
|
} else if (
|
||||||
navID == SettingsNavIDs.Mail &&
|
navID == SettingsNavIDs.Mail &&
|
||||||
(!this.mailAccounts || !this.mailRules)
|
(!this.mailAccounts || !this.mailRules)
|
||||||
@ -271,6 +301,50 @@ export class SettingsComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.users && this.groups) {
|
||||||
|
for (let user of this.users) {
|
||||||
|
storeData.usersGroup[user.id.toString()] = {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
first_name: user.first_name,
|
||||||
|
last_name: user.last_name,
|
||||||
|
is_active: user.is_active,
|
||||||
|
is_superuser: user.is_superuser,
|
||||||
|
groups: user.groups,
|
||||||
|
user_permissions: user.user_permissions,
|
||||||
|
}
|
||||||
|
this.usersGroup.addControl(
|
||||||
|
user.id.toString(),
|
||||||
|
new FormGroup({
|
||||||
|
id: new FormControl(null),
|
||||||
|
username: new FormControl(null),
|
||||||
|
first_name: new FormControl(null),
|
||||||
|
last_name: new FormControl(null),
|
||||||
|
is_active: new FormControl(null),
|
||||||
|
is_superuser: new FormControl(null),
|
||||||
|
groups: new FormControl(null),
|
||||||
|
user_permissions: new FormControl(null),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let group of this.groups) {
|
||||||
|
storeData.groupsGroup[group.id.toString()] = {
|
||||||
|
id: group.id,
|
||||||
|
name: group.name,
|
||||||
|
permissions: group.permissions,
|
||||||
|
}
|
||||||
|
this.groupsGroup.addControl(
|
||||||
|
group.id.toString(),
|
||||||
|
new FormGroup({
|
||||||
|
id: new FormControl(null),
|
||||||
|
name: new FormControl(null),
|
||||||
|
permissions: new FormControl(null),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.mailAccounts && this.mailRules) {
|
if (this.mailAccounts && this.mailRules) {
|
||||||
for (let account of this.mailAccounts) {
|
for (let account of this.mailAccounts) {
|
||||||
storeData.mailAccounts[account.id.toString()] = {
|
storeData.mailAccounts[account.id.toString()] = {
|
||||||
@ -547,6 +621,120 @@ export class SettingsComponent
|
|||||||
this.settingsForm.get('themeColor').patchValue('')
|
this.settingsForm.get('themeColor').patchValue('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editUser(user: PaperlessUser) {
|
||||||
|
var modal = this.modalService.open(UserEditDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
size: 'xl',
|
||||||
|
})
|
||||||
|
modal.componentInstance.dialogMode = user ? 'edit' : 'create'
|
||||||
|
modal.componentInstance.object = user
|
||||||
|
modal.componentInstance.succeeded
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe({
|
||||||
|
next: (newUser) => {
|
||||||
|
this.toastService.showInfo(
|
||||||
|
$localize`Saved user "${newUser.username}".`
|
||||||
|
)
|
||||||
|
this.usersService.listAll().subscribe((r) => {
|
||||||
|
this.users = r.results
|
||||||
|
this.initialize()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError(
|
||||||
|
$localize`Error saving user: ${e.toString()}.`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUser(user: PaperlessUser) {
|
||||||
|
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.title = $localize`Confirm delete user account`
|
||||||
|
modal.componentInstance.messageBold = $localize`This operation will permanently this user account.`
|
||||||
|
modal.componentInstance.message = $localize`This operation cannot be undone.`
|
||||||
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
this.usersService.delete(user).subscribe({
|
||||||
|
next: () => {
|
||||||
|
modal.close()
|
||||||
|
this.toastService.showInfo($localize`Deleted user`)
|
||||||
|
this.usersService.listAll().subscribe((r) => {
|
||||||
|
this.users = r.results
|
||||||
|
this.initialize()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError(
|
||||||
|
$localize`Error deleting user: ${e.toString()}.`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editGroup(group: PaperlessGroup) {
|
||||||
|
var modal = this.modalService.open(GroupEditDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
size: 'lg',
|
||||||
|
})
|
||||||
|
modal.componentInstance.dialogMode = group ? 'edit' : 'create'
|
||||||
|
modal.componentInstance.object = group
|
||||||
|
modal.componentInstance.succeeded
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe({
|
||||||
|
next: (newGroup) => {
|
||||||
|
this.toastService.showInfo($localize`Saved group "${newGroup.name}".`)
|
||||||
|
this.groupsService.listAll().subscribe((r) => {
|
||||||
|
this.groups = r.results
|
||||||
|
this.initialize()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError(
|
||||||
|
$localize`Error saving group: ${e.toString()}.`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteGroup(group: PaperlessGroup) {
|
||||||
|
let modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.title = $localize`Confirm delete user group`
|
||||||
|
modal.componentInstance.messageBold = $localize`This operation will permanently this user group.`
|
||||||
|
modal.componentInstance.message = $localize`This operation cannot be undone.`
|
||||||
|
modal.componentInstance.btnClass = 'btn-danger'
|
||||||
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
|
modal.componentInstance.confirmClicked.subscribe(() => {
|
||||||
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
this.groupsService.delete(group).subscribe({
|
||||||
|
next: () => {
|
||||||
|
modal.close()
|
||||||
|
this.toastService.showInfo($localize`Deleted group`)
|
||||||
|
this.groupsService.listAll().subscribe((r) => {
|
||||||
|
this.groups = r.results
|
||||||
|
this.initialize()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError(
|
||||||
|
$localize`Error deleting group: ${e.toString()}.`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getGroupName(id: number): string {
|
||||||
|
return this.groups?.find((g) => g.id === id)?.name ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
editMailAccount(account: PaperlessMailAccount) {
|
editMailAccount(account: PaperlessMailAccount) {
|
||||||
const modal = this.modalService.open(MailAccountEditDialogComponent, {
|
const modal = this.modalService.open(MailAccountEditDialogComponent, {
|
||||||
backdrop: 'static',
|
backdrop: 'static',
|
||||||
|
@ -3,6 +3,10 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { FILTER_STORAGE_PATH } from 'src/app/data/filter-rule-type'
|
import { FILTER_STORAGE_PATH } from 'src/app/data/filter-rule-type'
|
||||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
|
import {
|
||||||
|
PermissionsService,
|
||||||
|
PermissionType,
|
||||||
|
} from 'src/app/services/permissions.service'
|
||||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||||
@ -18,7 +22,8 @@ export class StoragePathListComponent extends ManagementListComponent<PaperlessS
|
|||||||
directoryService: StoragePathService,
|
directoryService: StoragePathService,
|
||||||
modalService: NgbModal,
|
modalService: NgbModal,
|
||||||
toastService: ToastService,
|
toastService: ToastService,
|
||||||
documentListViewService: DocumentListViewService
|
documentListViewService: DocumentListViewService,
|
||||||
|
permissionsService: PermissionsService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
directoryService,
|
directoryService,
|
||||||
@ -26,9 +31,11 @@ export class StoragePathListComponent extends ManagementListComponent<PaperlessS
|
|||||||
StoragePathEditDialogComponent,
|
StoragePathEditDialogComponent,
|
||||||
toastService,
|
toastService,
|
||||||
documentListViewService,
|
documentListViewService,
|
||||||
|
permissionsService,
|
||||||
FILTER_STORAGE_PATH,
|
FILTER_STORAGE_PATH,
|
||||||
$localize`storage path`,
|
$localize`storage path`,
|
||||||
$localize`storage paths`,
|
$localize`storage paths`,
|
||||||
|
PermissionType.StoragePath,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
key: 'path',
|
key: 'path',
|
||||||
|
@ -3,6 +3,10 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
|||||||
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
||||||
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
import { PaperlessTag } from 'src/app/data/paperless-tag'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
|
import {
|
||||||
|
PermissionsService,
|
||||||
|
PermissionType,
|
||||||
|
} from 'src/app/services/permissions.service'
|
||||||
import { TagService } from 'src/app/services/rest/tag.service'
|
import { TagService } from 'src/app/services/rest/tag.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||||
@ -18,7 +22,8 @@ export class TagListComponent extends ManagementListComponent<PaperlessTag> {
|
|||||||
tagService: TagService,
|
tagService: TagService,
|
||||||
modalService: NgbModal,
|
modalService: NgbModal,
|
||||||
toastService: ToastService,
|
toastService: ToastService,
|
||||||
documentListViewService: DocumentListViewService
|
documentListViewService: DocumentListViewService,
|
||||||
|
permissionsService: PermissionsService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
tagService,
|
tagService,
|
||||||
@ -26,9 +31,11 @@ export class TagListComponent extends ManagementListComponent<PaperlessTag> {
|
|||||||
TagEditDialogComponent,
|
TagEditDialogComponent,
|
||||||
toastService,
|
toastService,
|
||||||
documentListViewService,
|
documentListViewService,
|
||||||
|
permissionsService,
|
||||||
FILTER_HAS_TAGS_ALL,
|
FILTER_HAS_TAGS_ALL,
|
||||||
$localize`tag`,
|
$localize`tag`,
|
||||||
$localize`tags`,
|
$localize`tags`,
|
||||||
|
PermissionType.Tag,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
key: 'color',
|
key: 'color',
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
<use xlink:href="assets/bootstrap-icons.svg#x"/>
|
||||||
</svg> <ng-container i18n>Clear selection</ng-container>
|
</svg> <ng-container i18n>Clear selection</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" [disabled]="tasksService.total === 0">
|
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="tasksService.total === 0">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#check2-all"/>
|
<use xlink:href="assets/bootstrap-icons.svg#check2-all"/>
|
||||||
</svg> <ng-container i18n>{{dismissButtonText}}</ng-container>
|
</svg> <ng-container i18n>{{dismissButtonText}}</ng-container>
|
||||||
@ -75,16 +75,18 @@
|
|||||||
</td>
|
</td>
|
||||||
<td scope="row">
|
<td scope="row">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();">
|
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *appIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
<use xlink:href="assets/bootstrap-icons.svg#check"/>
|
||||||
</svg> <ng-container i18n>Dismiss</ng-container>
|
</svg> <ng-container i18n>Dismiss</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="task.related_document" class="btn btn-sm btn-outline-primary" (click)="dismissAndGo(task); $event.stopPropagation();">
|
<ng-container *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||||
<svg class="sidebaricon" fill="currentColor">
|
<button *ngIf="task.related_document" class="btn btn-sm btn-outline-primary" (click)="dismissAndGo(task); $event.stopPropagation();">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
<svg class="sidebaricon" fill="currentColor">
|
||||||
</svg> <ng-container i18n>Open Document</ng-container>
|
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
|
||||||
</button>
|
</svg> <ng-container i18n>Open Document</ng-container>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -5,13 +5,17 @@ import { Subject, first } from 'rxjs'
|
|||||||
import { PaperlessTask } from 'src/app/data/paperless-task'
|
import { PaperlessTask } from 'src/app/data/paperless-task'
|
||||||
import { TasksService } from 'src/app/services/tasks.service'
|
import { TasksService } from 'src/app/services/tasks.service'
|
||||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||||
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tasks',
|
selector: 'app-tasks',
|
||||||
templateUrl: './tasks.component.html',
|
templateUrl: './tasks.component.html',
|
||||||
styleUrls: ['./tasks.component.scss'],
|
styleUrls: ['./tasks.component.scss'],
|
||||||
})
|
})
|
||||||
export class TasksComponent implements OnInit, OnDestroy {
|
export class TasksComponent
|
||||||
|
extends ComponentWithPermissions
|
||||||
|
implements OnInit, OnDestroy
|
||||||
|
{
|
||||||
public activeTab: string
|
public activeTab: string
|
||||||
public selectedTasks: Set<number> = new Set()
|
public selectedTasks: Set<number> = new Set()
|
||||||
private unsubscribeNotifer = new Subject()
|
private unsubscribeNotifer = new Subject()
|
||||||
@ -27,7 +31,9 @@ export class TasksComponent implements OnInit, OnDestroy {
|
|||||||
public tasksService: TasksService,
|
public tasksService: TasksService,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private readonly router: Router
|
private readonly router: Router
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.tasksService.reload()
|
this.tasksService.reload()
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import {
|
||||||
|
PermissionAction,
|
||||||
|
PermissionType,
|
||||||
|
} from 'src/app/services/permissions.service'
|
||||||
|
|
||||||
|
export class ComponentWithPermissions {
|
||||||
|
public readonly PermissionAction = PermissionAction
|
||||||
|
public readonly PermissionType = PermissionType
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { ObjectWithId } from './object-with-id'
|
import { ObjectWithPermissions } from './object-with-permissions'
|
||||||
|
|
||||||
export const MATCH_ANY = 1
|
export const MATCH_ANY = 1
|
||||||
export const MATCH_ALL = 2
|
export const MATCH_ALL = 2
|
||||||
@ -41,7 +41,7 @@ export const MATCHING_ALGORITHMS = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export interface MatchingModel extends ObjectWithId {
|
export interface MatchingModel extends ObjectWithPermissions {
|
||||||
name?: string
|
name?: string
|
||||||
|
|
||||||
slug?: string
|
slug?: string
|
||||||
|
19
src-ui/src/app/data/object-with-permissions.ts
Normal file
19
src-ui/src/app/data/object-with-permissions.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ObjectWithId } from './object-with-id'
|
||||||
|
import { PaperlessUser } from './paperless-user'
|
||||||
|
|
||||||
|
export interface PermissionsObject {
|
||||||
|
view: {
|
||||||
|
users: Array<number>
|
||||||
|
groups: Array<number>
|
||||||
|
}
|
||||||
|
change: {
|
||||||
|
users: Array<number>
|
||||||
|
groups: Array<number>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ObjectWithPermissions extends ObjectWithId {
|
||||||
|
owner?: number
|
||||||
|
|
||||||
|
permissions?: PermissionsObject
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import { ObjectWithId } from './object-with-id'
|
import { ObjectWithId } from './object-with-id'
|
||||||
import { User } from './user'
|
import { PaperlessUser } from './paperless-user'
|
||||||
|
|
||||||
export interface PaperlessDocumentComment extends ObjectWithId {
|
export interface PaperlessDocumentComment extends ObjectWithId {
|
||||||
created?: Date
|
created?: Date
|
||||||
comment?: string
|
comment?: string
|
||||||
user?: User
|
user?: PaperlessUser
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { PaperlessCorrespondent } from './paperless-correspondent'
|
import { PaperlessCorrespondent } from './paperless-correspondent'
|
||||||
import { ObjectWithId } from './object-with-id'
|
|
||||||
import { PaperlessTag } from './paperless-tag'
|
import { PaperlessTag } from './paperless-tag'
|
||||||
import { PaperlessDocumentType } from './paperless-document-type'
|
import { PaperlessDocumentType } from './paperless-document-type'
|
||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { PaperlessStoragePath } from './paperless-storage-path'
|
import { PaperlessStoragePath } from './paperless-storage-path'
|
||||||
|
import { ObjectWithPermissions } from './object-with-permissions'
|
||||||
|
|
||||||
export interface SearchHit {
|
export interface SearchHit {
|
||||||
score?: number
|
score?: number
|
||||||
@ -13,7 +13,7 @@ export interface SearchHit {
|
|||||||
comment_highlights?: string
|
comment_highlights?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaperlessDocument extends ObjectWithId {
|
export interface PaperlessDocument extends ObjectWithPermissions {
|
||||||
correspondent$?: Observable<PaperlessCorrespondent>
|
correspondent$?: Observable<PaperlessCorrespondent>
|
||||||
|
|
||||||
correspondent?: number
|
correspondent?: number
|
||||||
|
9
src-ui/src/app/data/paperless-group.ts
Normal file
9
src-ui/src/app/data/paperless-group.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { ObjectWithId } from './object-with-id'
|
||||||
|
|
||||||
|
export interface PaperlessGroup extends ObjectWithId {
|
||||||
|
name?: string
|
||||||
|
|
||||||
|
user_count?: number // not implemented yet
|
||||||
|
|
||||||
|
permissions?: string[]
|
||||||
|
}
|
@ -1,11 +1,8 @@
|
|||||||
export interface PaperlessUiSettings {
|
export interface PaperlessUiSettings {
|
||||||
user_id: number
|
user_id: number
|
||||||
|
|
||||||
username: string
|
username: string
|
||||||
|
|
||||||
display_name: string
|
|
||||||
|
|
||||||
settings: Object
|
settings: Object
|
||||||
|
permissions: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaperlessUiSetting {
|
export interface PaperlessUiSetting {
|
||||||
|
15
src-ui/src/app/data/paperless-user.ts
Normal file
15
src-ui/src/app/data/paperless-user.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { PaperlessGroup } from 'src/app/data/paperless-group'
|
||||||
|
import { ObjectWithId } from './object-with-id'
|
||||||
|
|
||||||
|
export interface PaperlessUser extends ObjectWithId {
|
||||||
|
username?: string
|
||||||
|
first_name?: string
|
||||||
|
last_name?: string
|
||||||
|
date_joined?: Date
|
||||||
|
is_staff?: boolean
|
||||||
|
is_active?: boolean
|
||||||
|
is_superuser?: boolean
|
||||||
|
groups?: PaperlessGroup[]
|
||||||
|
user_permissions?: string[]
|
||||||
|
inherited_permissions?: string[]
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user