Compare commits

...
49 Commits
Author SHA1 Message Date
shamoon 1ba1afdce5 Bump version to 2.6.0 2024-03-04 20:20:46 -08:00
shamoon a98317c52a Fix: search field clear button padding 2024-03-04 20:13:52 -08:00
ffddd0f323 New Crowdin translations by GitHub Action (#5896)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-03-05 03:14:30 +00:00
Trenton H 4cb2f0acef Rolls up the rest of the random updates that dependabot didn't do for some reason (#6005) 2024-03-05 03:00:18 +00:00
shamoon 98663e902f Update translation strings 2024-03-04 17:00:57 -08:00
shamoon 0f5e935214 Fix: header button wrapping issues on mobile 2024-03-04 17:00:19 -08:00
Trenton H b9636a3def Feature: Allow user to control PIL image pixel limit (#5997) 2024-03-05 00:19:56 +00:00
shamoon 35574f3b86 Fix: prevent actions button click toggling list selection 2024-03-04 16:06:23 -08:00
shamoon 00a8f0cd6e Enhancement: show ID when editing objects (#6003) 2024-03-04 23:21:48 +00:00
Trenton H 6779042242 Feature: Allow a user to disable the pixel limit for OCR entirely (#5996) 2024-03-04 22:37:36 +00:00
dependabot[bot] 6379e7b54f Chore(deps): Bump the small-changes group with 3 updates (#6001)
Bumps the small-changes group with 3 updates: [python-dateutil](https://github.com/dateutil/dateutil), [python-ipware](https://github.com/un33k/python-ipware) and [redis](https://github.com/redis/redis-py).


Updates `python-dateutil` from 2.8.2 to 2.9.0.post0
- [Release notes](https://github.com/dateutil/dateutil/releases)
- [Changelog](https://github.com/dateutil/dateutil/blob/master/NEWS)
- [Commits](https://github.com/dateutil/dateutil/compare/2.8.2...2.9.0.post0)

Updates `python-ipware` from 2.0.1 to 2.0.2
- [Changelog](https://github.com/un33k/python-ipware/blob/main/CHANGELOG.md)
- [Commits](https://github.com/un33k/python-ipware/compare/v2.0.1...v2.0.2)

Updates `redis` from 5.0.1 to 5.0.2
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](https://github.com/redis/redis-py/compare/v5.0.1...v5.0.2)

---
updated-dependencies:
- dependency-name: python-dateutil
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: python-ipware
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: redis
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 22:27:59 +00:00
dependabot[bot]andTrenton H 2fa742c94b Chore(deps-dev): Bump the development group with 2 updates (#5998)
* Chore(deps-dev): Bump the development group with 2 updates

Bumps the development group with 2 updates: [ruff](https://github.com/astral-sh/ruff) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.2.2 to 0.3.0
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.2.2...v0.3.0)

Updates `mkdocs-material` from 9.5.11 to 9.5.12
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.11...9.5.12)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>

* Updates ruff hook version as well

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2024-03-04 22:08:13 +00:00
dependabot[bot] aa0da2f516 Chore(deps): Bump the django group with 1 update (#6000)
Bumps the django group with 1 update: [django](https://github.com/django/django).


Updates `django` from 4.2.10 to 4.2.11
- [Commits](https://github.com/django/django/compare/4.2.10...4.2.11)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: django
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 13:15:03 -08:00
shamoonandTrenton H f07441a408 Feature: workflow removal action (#5928)
---------

Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2024-03-04 17:37:42 +00:00
shamoon f6084acfc8 Feature: system status (#5743) 2024-03-04 09:26:25 -08:00
shamoon 23ceb2a5ec Fix: refactor base path settings, correct logout redirect (#5976) 2024-03-03 08:22:23 -08:00
dependabot[bot]andshamoon a698791059 Chore(deps-dev): Bump @playwright/test from 1.41.2 to 1.42.0 in /src-ui (#5964)
* Chore(deps-dev): Bump @playwright/test from 1.41.2 to 1.42.0 in /src-ui

Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.41.2 to 1.42.0.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.41.2...v1.42.0)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Revert .har changes from playwright bug

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-03-01 21:07:08 +00:00
dependabot[bot] 41c1f38ab2 Chore(deps-dev): Bump @types/node from 20.11.20 to 20.11.24 in /src-ui (#5965)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.20 to 20.11.24.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-01 21:00:09 +00:00
dependabot[bot] 83c85dc10e Chore(deps): Bump the frontend-angular-dependencies group (#5963)
Bumps the frontend-angular-dependencies group in /src-ui with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `17.2.2` | `17.2.3` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `17.2.2` | `17.2.3` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `17.2.2` | `17.2.3` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `17.2.2` | `17.2.3` |
| [@angular/localize](https://github.com/angular/angular) | `17.2.2` | `17.2.3` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `17.2.2` | `17.2.3` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `17.2.2` | `17.2.3` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `17.2.2` | `17.2.3` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `17.2.1` | `17.2.2` |
| [@angular/cli](https://github.com/angular/angular-cli) | `17.2.1` | `17.2.2` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `17.2.2` | `17.2.3` |


Updates `@angular/common` from 17.2.2 to 17.2.3
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.3/packages/common)

Updates `@angular/compiler` from 17.2.2 to 17.2.3
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.3/packages/compiler)

Updates `@angular/core` from 17.2.2 to 17.2.3
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.3/packages/core)

Updates `@angular/forms` from 17.2.2 to 17.2.3
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.3/packages/forms)

Updates `@angular/localize` from 17.2.2 to 17.2.3
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/compare/17.2.2...17.2.3)

Updates `@angular/platform-browser` from 17.2.2 to 17.2.3
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.3/packages/platform-browser)

Updates `@angular/platform-browser-dynamic` from 17.2.2 to 17.2.3
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.3/packages/platform-browser-dynamic)

Updates `@angular/router` from 17.2.2 to 17.2.3
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.3/packages/router)

Updates `@angular-devkit/build-angular` from 17.2.1 to 17.2.2
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/17.2.1...17.2.2)

Updates `@angular/cli` from 17.2.1 to 17.2.2
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/17.2.1...17.2.2)

Updates `@angular/compiler-cli` from 17.2.2 to 17.2.3
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.3/packages/compiler-cli)

---
updated-dependencies:
- dependency-name: "@angular/common"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-01 20:51:12 +00:00
shamoon a020d807d4 Fix: always pass remove_inbox_tags from UI, dont require in API (#5962) 2024-03-01 12:00:54 -08:00
shamoon 464ee51de8 Fix: mobile sizing tweaks 2024-02-29 16:57:47 -08:00
Trenton Handshamoon c57c1d5389 Fix: Clear metadata cache when the filename(s) change (#5957)
* Update doc modified time upon move and rename

* Clear the cached metadata if the filename(s) have been changed

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-02-29 10:34:15 -08:00
shamoon af16bb3934 Fix: include monetary, float and doc link values in search filters (#5951) 2024-02-29 08:21:56 +00:00
Trenton H fba416e8e1 Fixes a corrupted index not being handled by whoosh.create_in (#5950) 2024-02-29 00:07:17 +00:00
Trenton H 3d8de50b5a Don't assume the /tmp folder is where the user wants to place the scratch directory (#5948) 2024-02-28 22:46:24 +00:00
Trenton H 86263a52ea Includes OCRMyPdf logging into the log file (#5947) 2024-02-28 14:39:42 -08:00
shamoon 754627681c Fix: ensure document title always limited to 128 chars (#5934) 2024-02-28 07:05:19 -08:00
shamoon bf11dc8d1b Enhancement: better monetary field with currency code (#5858) 2024-02-27 16:26:06 +00:00
Dennis Gaida 84721b001f Change: add Thumbs.db to default ignores (#5924)
* Adding Thumbs.db to default ignores

Thanks Windows for creating these great little database files everywhere

* Updating configuration with new default
2024-02-27 07:48:50 -08:00
Felix Eckhofer f48a20c75f Documentation: fix typo (#5922) 2024-02-27 00:25:18 -08:00
shamoon 16f4552e0e Fix: use PAPERLESS_URL if set for pw reset emails (#5902) 2024-02-26 21:41:25 +00:00
ShanSanear 86811d0733 Fix: Correcting check searching for docker compose plugin (#5904) (#5917) 2024-02-26 21:21:43 +00:00
shamoon d2f9b5d5e5 Fix: respect global permissions for UI settings (#5919) 2024-02-26 20:19:31 +00:00
shamoon f5e1675107 Fix: tasks badge position on mobile 2024-02-26 12:02:45 -08:00
dependabot[bot] ae016cae4b Chore(deps-dev): Bump the frontend-eslint-dependencies group (#5918)
Bumps the frontend-eslint-dependencies group in /src-ui with 3 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) and [eslint](https://github.com/eslint/eslint).


Updates `@typescript-eslint/eslint-plugin` from 6.20.0 to 7.1.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.1.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 6.20.0 to 7.1.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.1.0/packages/parser)

Updates `eslint` from 8.56.0 to 8.57.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.56.0...v8.57.0)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: frontend-eslint-dependencies
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 11:22:26 -08:00
dependabot[bot] c7f6d03508 Chore(deps-dev): Bump @types/node from 20.11.16 to 20.11.20 in /src-ui (#5912)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.16 to 20.11.20.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 18:55:52 +00:00
dependabot[bot] 955f2d0db9 Chore(deps): Bump zone.js from 0.14.3 to 0.14.4 in /src-ui (#5913)
Bumps [zone.js](https://github.com/angular/angular/tree/HEAD/packages/zone.js) from 0.14.3 to 0.14.4.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/packages/zone.js/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/zone.js-0.14.4/packages/zone.js)

---
updated-dependencies:
- dependency-name: zone.js
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 18:30:58 +00:00
dependabot[bot] ba32684df6 Chore(deps): Bump bootstrap from 5.3.2 to 5.3.3 in /src-ui (#5911)
Bumps [bootstrap](https://github.com/twbs/bootstrap) from 5.3.2 to 5.3.3.
- [Release notes](https://github.com/twbs/bootstrap/releases)
- [Commits](https://github.com/twbs/bootstrap/compare/v5.3.2...v5.3.3)

---
updated-dependencies:
- dependency-name: bootstrap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 18:20:03 +00:00
dependabot[bot] 2f7adf40ac Chore(deps-dev): Bump typescript from 5.2.2 to 5.3.3 in /src-ui (#5915)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.2.2 to 5.3.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.2.2...v5.3.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 18:08:50 +00:00
dependabot[bot] a52031161b Chore(deps): Bump the frontend-angular-dependencies group (#5908)
Bumps the frontend-angular-dependencies group in /src-ui with 15 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `17.1.2` | `17.2.1` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `17.1.2` | `17.2.2` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `17.1.2` | `17.2.2` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `17.1.2` | `17.2.2` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `17.1.2` | `17.2.2` |
| [@angular/localize](https://github.com/angular/angular) | `17.1.2` | `17.2.2` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `17.1.2` | `17.2.2` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `17.1.2` | `17.2.2` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `17.1.2` | `17.2.2` |
| [@ng-select/ng-select](https://github.com/ng-select/ng-select) | `12.0.6` | `12.0.7` |
| [ngx-cookie-service](https://github.com/stevermeister/ngx-cookie-service) | `17.0.1` | `17.1.0` |
| [@angular-builders/jest](https://github.com/just-jeb/angular-builders/tree/HEAD/packages/jest) | `17.0.0` | `17.0.2` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `17.2.0` | `17.2.1` |
| [@angular/cli](https://github.com/angular/angular-cli) | `17.1.2` | `17.2.1` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `17.1.2` | `17.2.2` |


Updates `@angular/cdk` from 17.1.2 to 17.2.1
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/17.1.2...17.2.1)

Updates `@angular/common` from 17.1.2 to 17.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.2/packages/common)

Updates `@angular/compiler` from 17.1.2 to 17.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.2/packages/compiler)

Updates `@angular/core` from 17.1.2 to 17.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.2/packages/core)

Updates `@angular/forms` from 17.1.2 to 17.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.2/packages/forms)

Updates `@angular/localize` from 17.1.2 to 17.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/compare/17.1.2...17.2.2)

Updates `@angular/platform-browser` from 17.1.2 to 17.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.2/packages/platform-browser)

Updates `@angular/platform-browser-dynamic` from 17.1.2 to 17.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.2/packages/platform-browser-dynamic)

Updates `@angular/router` from 17.1.2 to 17.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.2/packages/router)

Updates `@ng-select/ng-select` from 12.0.6 to 12.0.7
- [Release notes](https://github.com/ng-select/ng-select/releases)
- [Changelog](https://github.com/ng-select/ng-select/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ng-select/ng-select/compare/v12.0.6...v12.0.7)

Updates `ngx-cookie-service` from 17.0.1 to 17.1.0
- [Release notes](https://github.com/stevermeister/ngx-cookie-service/releases)
- [Changelog](https://github.com/stevermeister/ngx-cookie-service/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stevermeister/ngx-cookie-service/compare/v17.0.1...v17.1.0)

Updates `@angular-builders/jest` from 17.0.0 to 17.0.2
- [Release notes](https://github.com/just-jeb/angular-builders/releases)
- [Changelog](https://github.com/just-jeb/angular-builders/blob/master/packages/jest/CHANGELOG.md)
- [Commits](https://github.com/just-jeb/angular-builders/commits/@angular-builders/jest@17.0.2/packages/jest)

Updates `@angular-devkit/build-angular` from 17.2.0 to 17.2.1
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/17.2.0...17.2.1)

Updates `@angular/cli` from 17.1.2 to 17.2.1
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/17.1.2...17.2.1)

Updates `@angular/compiler-cli` from 17.1.2 to 17.2.2
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/17.2.2/packages/compiler-cli)

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/common"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@ng-select/ng-select"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: ngx-cookie-service
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-builders/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 17:58:26 +00:00
shamoon 3e2c541b7b Fix: allow disable allauth email verification (#5895) 2024-02-26 09:33:59 -08:00
shamoon 967fc98090 Fix: Refactor accounts templates and create signup template (#5899) 2024-02-26 09:23:53 -08:00
dependabot[bot] 22e95f45bd Chore(deps): Bump the small-changes group with 4 updates (#5916)
Bumps the small-changes group with 4 updates: [scikit-learn](https://github.com/scikit-learn/scikit-learn), [tqdm](https://github.com/tqdm/tqdm), [pre-commit](https://github.com/pre-commit/pre-commit) and [daphne](https://github.com/django/daphne).


Updates `scikit-learn` from 1.4.0 to 1.4.1.post1
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.4.0...1.4.1.post1)

Updates `tqdm` from 4.66.1 to 4.66.2
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.1...v4.66.2)

Updates `pre-commit` from 3.6.0 to 3.6.2
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.6.0...v3.6.2)

Updates `daphne` from 4.0.0 to 4.1.0
- [Changelog](https://github.com/django/daphne/blob/main/CHANGELOG.txt)
- [Commits](https://github.com/django/daphne/commits)

---
updated-dependencies:
- dependency-name: scikit-learn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: tqdm
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: pre-commit
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: daphne
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 09:11:59 -08:00
dependabot[bot]andTrenton H 38c777ec0f Chore(deps-dev): Bump the development group with 4 updates (#5914)
* Chore(deps-dev): Bump the development group with 4 updates

Bumps the development group with 4 updates: [ruff](https://github.com/astral-sh/ruff), [pytest](https://github.com/pytest-dev/pytest), [pytest-httpx](https://github.com/Colin-b/pytest_httpx) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.2.1 to 0.2.2
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.2.1...v0.2.2)

Updates `pytest` from 8.0.0 to 8.0.2
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.0.0...8.0.2)

Updates `pytest-httpx` from 0.29.0 to 0.30.0
- [Release notes](https://github.com/Colin-b/pytest_httpx/releases)
- [Changelog](https://github.com/Colin-b/pytest_httpx/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/Colin-b/pytest_httpx/compare/v0.29.0...v0.30.0)

Updates `mkdocs-material` from 9.5.9 to 9.5.11
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.9...9.5.11)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: pytest-httpx
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>

* Updates the hook versions to match installed versions

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2024-02-26 16:27:45 +00:00
dependabot[bot] ccbc97399a Chore(deps): Bump the actions group with 3 updates (#5907)
Bumps the actions group with 3 updates: [pre-commit/action](https://github.com/pre-commit/action), [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) and [stumpylog/image-cleaner-action](https://github.com/stumpylog/image-cleaner-action).


Updates `pre-commit/action` from 3.0.0 to 3.0.1
- [Release notes](https://github.com/pre-commit/action/releases)
- [Commits](https://github.com/pre-commit/action/compare/v3.0.0...v3.0.1)

Updates `release-drafter/release-drafter` from 5 to 6
- [Release notes](https://github.com/release-drafter/release-drafter/releases)
- [Commits](https://github.com/release-drafter/release-drafter/compare/v5...v6)

Updates `stumpylog/image-cleaner-action` from 0.4.0 to 0.5.0
- [Release notes](https://github.com/stumpylog/image-cleaner-action/releases)
- [Changelog](https://github.com/stumpylog/image-cleaner-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stumpylog/image-cleaner-action/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: pre-commit/action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: release-drafter/release-drafter
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: stumpylog/image-cleaner-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 08:08:15 -08:00
Trenton H f43013a746 Ignores uvicorn updates in dependabot (#5906) 2024-02-26 07:55:59 -08:00
shamoon 1335ab5f1b Enhancement: support disabling regular login (#5816) 2024-02-26 05:17:21 +00:00
shamoon 90b4691f16 Reset dev version string 2024-02-25 19:43:25 -08:00
github-actions[bot] f053ee3191 Changelog v2.5.4 - GHA (#5894)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-25 19:33:07 -08:00
190 changed files with 41451 additions and 23047 deletions

No files matched your search

+2
View File
@@ -47,6 +47,8 @@ updates:
# Add reviewers
reviewers:
- "paperless-ngx/backend"
ignore:
- dependency-name: "uvicorn"
groups:
development:
patterns:
+2 -2
View File
@@ -42,7 +42,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
-
name: Check files
uses: pre-commit/action@v3.0.0
uses: pre-commit/action@v3.0.1
documentation:
name: "Build & Deploy Documentation"
@@ -577,7 +577,7 @@ jobs:
-
name: Create Release and Changelog
id: create-release
uses: release-drafter/release-drafter@v5
uses: release-drafter/release-drafter@v6
with:
name: Paperless-ngx ${{ steps.get_version.outputs.version }}
tag: ${{ steps.get_version.outputs.version }}
+2 -2
View File
@@ -33,7 +33,7 @@ jobs:
-
name: Clean temporary images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/ephemeral@v0.4.0
uses: stumpylog/image-cleaner-action/ephemeral@v0.5.0
with:
token: "${{ env.TOKEN }}"
owner: "${{ github.repository_owner }}"
@@ -61,7 +61,7 @@ jobs:
-
name: Clean untagged images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/untagged@v0.4.0
uses: stumpylog/image-cleaner-action/untagged@v0.5.0
with:
token: "${{ env.TOKEN }}"
owner: "${{ github.repository_owner }}"
+1 -1
View File
@@ -22,6 +22,6 @@ jobs:
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
steps:
- name: Label PR with release-drafter
uses: release-drafter/release-drafter@v5
uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+7 -7
View File
@@ -28,7 +28,7 @@ jobs:
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.
for your contributions. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
lock-threads:
name: 'Lock Old Threads'
runs-on: ubuntu-latest
@@ -43,17 +43,17 @@ jobs:
This issue has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new discussion or issue for related concerns.
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
pr-comment: >
This pull request has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new discussion or issue for related concerns.
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
discussion-comment: >
This discussion has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new discussion for related concerns.
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
close-answered-discussions:
name: 'Close Answered Discussions'
runs-on: ubuntu-latest
@@ -93,7 +93,7 @@ jobs:
}`;
const commentVariables = {
discussion: discussion.id,
body: 'This discussion has been automatically closed because it was marked as answered. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.',
body: 'This discussion has been automatically closed because it was marked as answered. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
}
await github.graphql(addCommentMutation, commentVariables)
@@ -183,7 +183,7 @@ jobs:
}`;
const commentVariables = {
discussion: discussion.id,
body: 'This discussion has been automatically closed due to inactivity. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.',
body: 'This discussion has been automatically closed due to inactivity. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
}
await github.graphql(addCommentMutation, commentVariables);
@@ -261,7 +261,7 @@ jobs:
}`;
const commentVariables = {
discussion: discussion.id,
body: 'This discussion has been automatically closed due to lack of community support. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-respoistory-maintenance) for more details.',
body: 'This discussion has been automatically closed due to lack of community support. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
}
await github.graphql(addCommentMutation, commentVariables);
+2 -2
View File
@@ -47,11 +47,11 @@ repos:
exclude: "(^Pipfile\\.lock$)"
# Python hooks
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.2.1'
rev: 'v0.3.0'
hooks:
- id: ruff
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.1.1
rev: 24.2.0
hooks:
- id: black
# Dockerfile hooks
+1 -1
View File
@@ -138,7 +138,7 @@ We are not overly strict with inviting people to the organization. If you have r
The admins occasionally invite contributors directly if we believe having them on a team will accelerate their work.
# Automatic Respoistory Maintenance
# Automatic Repository Maintenance
The Paperless-ngx team appreciates all effort and interest from the community in filing bug reports, creating feature requests, sharing ideas and helping other
community members. That said, in an effort to keep the repository organized and managebale the project uses automatic handling of certain areas:
+2 -1
View File
@@ -59,7 +59,8 @@ ARG GS_VERSION=10.02.1
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
# Ignore warning from Whitenoise
PYTHONWARNINGS="ignore:::django.http.response:517"
PYTHONWARNINGS="ignore:::django.http.response:517" \
PNGX_CONTAINERIZED=1
#
# Begin installation and configuration
+1 -1
View File
@@ -7,7 +7,7 @@ name = "pypi"
dateparser = "~=1.2"
# WARNING: django does not use semver.
# Only patch versions are guaranteed to not introduce breaking changes.
django = "~=4.2.10"
django = "~=4.2.11"
django-allauth = "*"
django-auditlog = "*"
django-celery-results = "*"
Generated
+500 -506
View File
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -90,13 +90,13 @@ initialize() {
fi
done
local -r tmp_dir="/tmp/paperless"
echo "Creating directory ${tmp_dir}"
local -r tmp_dir="${PAPERLESS_SCRATCH_DIR:=/tmp/paperless}"
echo "Creating directory scratch directory ${tmp_dir}"
mkdir --parents "${tmp_dir}"
set +e
echo "Adjusting permissions of paperless files. This may take a while."
chown -R paperless:paperless ${tmp_dir}
chown -R paperless:paperless "${tmp_dir}"
for dir in \
"${export_dir}" \
"${DATA_DIR}" \
+5 -1
View File
@@ -437,7 +437,7 @@ with Prometheus, as it exports metrics. For details on its capabilities,
refer to the [Flower](https://flower.readthedocs.io/en/latest/index.html)
documentation.
Flower can be enabled with the setting [PAPERLESS_ENABLE_FLOWER](configuration/#PAPERLESS_ENABLE_FLOWER).
Flower can be enabled with the setting [PAPERLESS_ENABLE_FLOWER](configuration.md#PAPERLESS_ENABLE_FLOWER).
To configure Flower further, create a `flowerconfig.py` and
place it into the `src/paperless` directory. For a Docker
installation, you can use volumes to accomplish this:
@@ -692,3 +692,7 @@ PAPERLESS_SOCIALACCOUNT_PROVIDERS='
```
More details about configuration option for various providers can be found in the [allauth documentation](https://docs.allauth.org/en/latest/socialaccount/providers/index.html#provider-specifics).
### Disabling Regular Login
Once external auth is set up, 'regular' login can be disabled with the [PAPERLESS_DISABLE_REGULAR_LOGIN](configuration.md#PAPERLESS_DISABLE_REGULAR_LOGIN) setting.
+32
View File
@@ -1,5 +1,37 @@
# Changelog
## paperless-ngx 2.5.4
### Bug Fixes
- Fix: handle title placeholder for docs without original_filename [@shamoon](https://github.com/shamoon) ([#5828](https://github.com/paperless-ngx/paperless-ngx/pull/5828))
- Fix: bulk edit objects does not respect global permissions [@shamoon](https://github.com/shamoon) ([#5888](https://github.com/paperless-ngx/paperless-ngx/pull/5888))
- Fix: intermittent save \& close warnings [@shamoon](https://github.com/shamoon) ([#5838](https://github.com/paperless-ngx/paperless-ngx/pull/5838))
- Fix: inotify read timeout not in ms [@grembo](https://github.com/grembo) ([#5876](https://github.com/paperless-ngx/paperless-ngx/pull/5876))
- Fix: allow relative date queries not in quick list [@shamoon](https://github.com/shamoon) ([#5801](https://github.com/paperless-ngx/paperless-ngx/pull/5801))
- Fix: pass rule id to consumed .eml files [@shamoon](https://github.com/shamoon) ([#5800](https://github.com/paperless-ngx/paperless-ngx/pull/5800))
### Dependencies
- Chore(deps): Bump cryptography from 42.0.2 to 42.0.4 [@dependabot](https://github.com/dependabot) ([#5851](https://github.com/paperless-ngx/paperless-ngx/pull/5851))
- Chore(deps-dev): Bump ip from 2.0.0 to 2.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#5835](https://github.com/paperless-ngx/paperless-ngx/pull/5835))
- Chore(deps): Bump undici and [@<!---->angular-devkit/build-angular in /src-ui @dependabot](https://github.com/<!---->angular-devkit/build-angular in /src-ui @dependabot) ([#5796](https://github.com/paperless-ngx/paperless-ngx/pull/5796))
### All App Changes
<details>
<summary>8 changes</summary>
- Fix: handle title placeholder for docs without original_filename [@shamoon](https://github.com/shamoon) ([#5828](https://github.com/paperless-ngx/paperless-ngx/pull/5828))
- Fix: bulk edit objects does not respect global permissions [@shamoon](https://github.com/shamoon) ([#5888](https://github.com/paperless-ngx/paperless-ngx/pull/5888))
- Fix: intermittent save \& close warnings [@shamoon](https://github.com/shamoon) ([#5838](https://github.com/paperless-ngx/paperless-ngx/pull/5838))
- Fix: inotify read timeout not in ms [@grembo](https://github.com/grembo) ([#5876](https://github.com/paperless-ngx/paperless-ngx/pull/5876))
- Chore(deps-dev): Bump ip from 2.0.0 to 2.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#5835](https://github.com/paperless-ngx/paperless-ngx/pull/5835))
- Chore(deps): Bump undici and [@<!---->angular-devkit/build-angular in /src-ui @dependabot](https://github.com/<!---->angular-devkit/build-angular in /src-ui @dependabot) ([#5796](https://github.com/paperless-ngx/paperless-ngx/pull/5796))
- Fix: allow relative date queries not in quick list [@shamoon](https://github.com/shamoon) ([#5801](https://github.com/paperless-ngx/paperless-ngx/pull/5801))
- Fix: pass rule id to consumed .eml files [@shamoon](https://github.com/shamoon) ([#5800](https://github.com/paperless-ngx/paperless-ngx/pull/5800))
</details>
## paperless-ngx 2.5.3
### Bug Fixes
+35 -2
View File
@@ -572,6 +572,23 @@ system. See the corresponding
Defaults to 'https'
#### [`PAPERLESS_ACCOUNT_EMAIL_VERIFICATION=<string>`](#PAPERLESS_ACCOUNT_EMAIL_VERIFICATION) {#PAPERLESS_ACCOUNT_EMAIL_VERIFICATION}
: Determines whether email addresses are verified during signup (as performed by Django allauth). See the relevant
[paperless settings](#PAPERLESS_EMAIL_HOST) and [the allauth docs](https://docs.allauth.org/en/latest/account/configuration.html)
Defaults to 'optional'
!!! note
If you do not have a working email server set up you should set this to 'none'.
#### [`PAPERLESS_DISABLE_REGULAR_LOGIN=<bool>`](#PAPERLESS_DISABLE_REGULAR_LOGIN) {#PAPERLESS_DISABLE_REGULAR_LOGIN}
: Disables the regular frontend username / password login, i.e. once you have setup SSO. Note that the Django admin login cannot be disabled.
Defaults to False
## OCR settings {#ocr}
Paperless uses [OCRmyPDF](https://ocrmypdf.readthedocs.io/en/latest/)
@@ -749,6 +766,8 @@ but could result in missing text content.
If unset, will default to the value determined by
[Pillow](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.MAX_IMAGE_PIXELS).
Setting this value to 0 will entirely disable the limit. See the below warning.
!!! note
Increasing this limit could cause Paperless to consume additional
@@ -758,7 +777,7 @@ but could result in missing text content.
!!! warning
The limit is intended to prevent malicious files from consuming
system resources and causing crashes and other errors. Only increase
system resources and causing crashes and other errors. Only change
this value if you are certain your documents are not malicious and
you need the text which was not OCRed
@@ -950,6 +969,20 @@ be used with caution!
Defaults to None, which does not add any additional apps.
#### [`PAPERLESS_MAX_IMAGE_PIXELS=<number>`](#PAPERLESS_MAX_IMAGE_PIXELS) {#PAPERLESS_MAX_IMAGE_PIXELS}
: Configures the maximum size of an image PIL will allow to load without warning or error.
: If unset, will default to the value determined by
[Pillow](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.MAX_IMAGE_PIXELS).
Defaults to None, which does change the limit
!!! warning
This limit is designed to prevent denial of service from malicious files.
It should only be raised or disabled in certain circumstances and with great care.
## Document Consumption {#consume_config}
#### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES}
@@ -997,7 +1030,7 @@ or hidden folders some tools use to store data.
`._foo.pdf` and `._bar/foo.pdf`
Defaults to
`[".DS_STORE/*", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini", "@eaDir/*"]`.
`[".DS_Store", ".DS_STORE", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini", "@eaDir/*", "Thumbs.db"]`.
#### [`PAPERLESS_CONSUMER_BARCODE_SCANNER=<string>`](#PAPERLESS_CONSUMER_BARCODE_SCANNER) {#PAPERLESS_CONSUMER_BARCODE_SCANNER}
+11 -3
View File
@@ -253,7 +253,8 @@ permissions can be granted to limit access to certain parts of the UI (and corre
### Password reset
In order to enable the password reset feature you will need to setup an SMTP backend, see
[`PAPERLESS_EMAIL_HOST`](configuration.md#PAPERLESS_EMAIL_HOST)
[`PAPERLESS_EMAIL_HOST`](configuration.md#PAPERLESS_EMAIL_HOST). If your installation does not have
[`PAPERLESS_URL`](configuration.md#PAPERLESS_URL) set, the reset link included in emails will use the server host.
## Workflows
@@ -328,7 +329,7 @@ Workflows allow you to filter by:
### Workflow Actions
There is currently one type of workflow action, "Assignment", which can assign:
There are currently two types of workflow actions, "Assignment", which can assign:
- Title, see [title placeholders](usage.md#title-placeholders) below
- Tags, correspondent, document type and storage path
@@ -336,6 +337,13 @@ There is currently one type of workflow action, "Assignment", which can assign:
- View and / or edit permissions to users or groups
- Custom fields. Note that no value for the field will be set
and "Removal" actions, which can remove either all of or specific sets of the following:
- Tags, correspondents, document types or storage paths
- Document owner
- View and / or edit permissions
- Custom fields
#### Title placeholders
Workflow titles can include placeholders but the available options differ depending on the type of
@@ -406,7 +414,7 @@ The following custom field types are supported:
- `URL`: a valid url
- `Integer`: integer number e.g. 12
- `Number`: float number e.g. 12.3456
- `Monetary`: float number with exactly two decimals, e.g. 12.30
- `Monetary`: [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) and a number with exactly two decimals, e.g. USD12.30
- `Document Link`: reference(s) to other document(s) displayed as links, automatically creates a symmetrical link in reverse
## Share Links
+2 -2
View File
@@ -56,8 +56,8 @@ if ! command -v docker &> /dev/null ; then
exit 1
fi
if ! command -v docker compose &> /dev/null ; then
echo "docker compose executable not found. Is docker compose installed?"
if ! docker compose &> /dev/null ; then
echo "docker compose plugin not found. Is docker compose installed?"
exit 1
fi
+3 -1
View File
@@ -77,7 +77,9 @@
"scripts": [],
"allowedCommonJsDependencies": [
"pdfjs-dist",
"pdfjs-dist/web/pdf_viewer"
"pdfjs-dist/web/pdf_viewer",
"filesize",
"file-saver"
],
"vendorChunk": true,
"extractLicenses": false,
@@ -2700,7 +2700,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2734,7 +2734,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2768,7 +2768,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2802,7 +2802,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2836,7 +2836,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2870,7 +2870,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2904,7 +2904,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2938,7 +2938,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2972,7 +2972,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3006,7 +3006,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3040,7 +3040,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3074,7 +3074,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3108,7 +3108,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3142,7 +3142,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3176,7 +3176,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3210,7 +3210,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3244,7 +3244,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3278,7 +3278,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -3312,7 +3312,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -425,7 +425,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -470,7 +470,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -645,7 +645,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -685,7 +685,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -729,7 +729,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -843,7 +843,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -994,7 +994,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -996,7 +996,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1301,7 +1301,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1484,7 +1484,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1518,7 +1518,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1552,7 +1552,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1586,7 +1586,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1620,7 +1620,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1654,7 +1654,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1688,7 +1688,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1722,7 +1722,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1756,7 +1756,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1790,7 +1790,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1824,7 +1824,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1858,7 +1858,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1892,7 +1892,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1926,7 +1926,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1960,7 +1960,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -1994,7 +1994,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2028,7 +2028,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2062,7 +2062,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2096,7 +2096,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2130,7 +2130,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2164,7 +2164,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2198,7 +2198,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2232,7 +2232,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2266,7 +2266,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2300,7 +2300,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2334,7 +2334,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2368,7 +2368,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2402,7 +2402,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2436,7 +2436,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
@@ -2470,7 +2470,7 @@
"bodySize": -1
},
"response": {
"status": 200,
"status": -1,
"statusText": "",
"httpVersion": "HTTP/1.1",
"cookies": [],
+673 -395
View File
File diff suppressed because it is too large. Load diff
+442 -716
View File
File diff suppressed because it is too large. Load diff
+24 -23
View File
@@ -11,57 +11,58 @@
},
"private": true,
"dependencies": {
"@angular/cdk": "^17.1.2",
"@angular/common": "~17.1.2",
"@angular/compiler": "~17.1.2",
"@angular/core": "~17.1.2",
"@angular/forms": "~17.1.2",
"@angular/localize": "~17.1.2",
"@angular/platform-browser": "~17.1.2",
"@angular/platform-browser-dynamic": "~17.1.2",
"@angular/router": "~17.1.2",
"@angular/cdk": "^17.2.1",
"@angular/common": "~17.2.3",
"@angular/compiler": "~17.2.3",
"@angular/core": "~17.2.3",
"@angular/forms": "~17.2.3",
"@angular/localize": "~17.2.3",
"@angular/platform-browser": "~17.2.3",
"@angular/platform-browser-dynamic": "~17.2.3",
"@angular/router": "~17.2.3",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@ng-select/ng-select": "^12.0.6",
"@ng-select/ng-select": "^12.0.7",
"@ngneat/dirty-check-forms": "^3.0.3",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.2",
"bootstrap": "^5.3.3",
"file-saver": "^2.0.5",
"mime-names": "^1.0.0",
"ngx-bootstrap-icons": "^1.9.3",
"ngx-color": "^9.0.0",
"ngx-cookie-service": "^17.0.1",
"ngx-cookie-service": "^17.1.0",
"ngx-file-drop": "^16.0.0",
"ngx-filesize": "^3.0.3",
"ngx-ui-tour-ng-bootstrap": "^14.0.2",
"pdfjs-dist": "^3.11.174",
"rxjs": "^7.8.1",
"tslib": "^2.6.2",
"uuid": "^9.0.1",
"zone.js": "^0.14.3"
"zone.js": "^0.14.4"
},
"devDependencies": {
"@angular-builders/jest": "17.0.0",
"@angular-devkit/build-angular": "~17.2.0",
"@angular-builders/jest": "17.0.2",
"@angular-devkit/build-angular": "~17.2.2",
"@angular-eslint/builder": "17.2.1",
"@angular-eslint/eslint-plugin": "17.2.1",
"@angular-eslint/eslint-plugin-template": "17.2.1",
"@angular-eslint/schematics": "17.2.1",
"@angular-eslint/template-parser": "17.2.1",
"@angular/cli": "~17.1.2",
"@angular/compiler-cli": "~17.1.2",
"@playwright/test": "^1.41.2",
"@angular/cli": "~17.2.2",
"@angular/compiler-cli": "~17.2.2",
"@playwright/test": "^1.42.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.16",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
"@types/node": "^20.11.24",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"concurrently": "^8.2.2",
"eslint": "^8.56.0",
"eslint": "^8.57.0",
"jest": "29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-preset-angular": "^14.0.0",
"jest-websocket-mock": "^2.5.0",
"patch-package": "^8.0.0",
"ts-node": "~10.9.1",
"typescript": "^5.2.2",
"typescript": "^5.3.3",
"wait-on": "^7.2.0"
}
}
+1 -1
View File
@@ -163,7 +163,7 @@ export const routes: Routes = [
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
action: PermissionAction.Change,
type: PermissionType.UISettings,
},
},
+16
View File
@@ -113,7 +113,11 @@ import { ConfigComponent } from './components/admin/config/config.component'
import { FileComponent } from './components/common/input/file/file.component'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { ConfirmButtonComponent } from './components/common/confirm-button/confirm-button.component'
import { MonetaryComponent } from './components/common/input/monetary/monetary.component'
import { SystemStatusDialogComponent } from './components/common/system-status-dialog/system-status-dialog.component'
import { NgxFilesizeModule } from 'ngx-filesize'
import {
airplane,
archive,
arrowCounterclockwise,
arrowDown,
@@ -128,12 +132,14 @@ import {
boxes,
calendar,
calendarEvent,
cardChecklist,
caretDown,
caretUp,
chatLeftText,
check,
check2All,
checkAll,
checkCircleFill,
checkLg,
chevronDoubleLeft,
chevronDoubleRight,
@@ -147,7 +153,9 @@ import {
doorOpen,
download,
envelope,
exclamationCircleFill,
exclamationTriangle,
exclamationTriangleFill,
eye,
fileEarmark,
fileEarmarkCheck,
@@ -199,6 +207,7 @@ import {
} from 'ngx-bootstrap-icons'
const icons = {
airplane,
archive,
arrowCounterclockwise,
arrowDown,
@@ -213,12 +222,14 @@ const icons = {
boxes,
calendar,
calendarEvent,
cardChecklist,
caretDown,
caretUp,
chatLeftText,
check,
check2All,
checkAll,
checkCircleFill,
checkLg,
chevronDoubleLeft,
chevronDoubleRight,
@@ -232,7 +243,9 @@ const icons = {
doorOpen,
download,
envelope,
exclamationCircleFill,
exclamationTriangle,
exclamationTriangleFill,
eye,
fileEarmark,
fileEarmarkCheck,
@@ -443,6 +456,8 @@ function initializeApp(settings: SettingsService) {
ConfigComponent,
FileComponent,
ConfirmButtonComponent,
MonetaryComponent,
SystemStatusDialogComponent,
],
imports: [
BrowserModule,
@@ -457,6 +472,7 @@ function initializeApp(settings: SettingsService) {
TourNgBootstrapModule,
DragDropModule,
NgxBootstrapIconsModule.pick(icons),
NgxFilesizeModule,
],
providers: [
{
@@ -4,10 +4,31 @@
info="Options to customize appearance, notifications, saved views and more. Settings apply to the <strong>current user only</strong>."
i18n-info
>
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()"><ng-container i18n>Start tour</ng-container></button>
<a *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" class="btn btn-sm btn-primary ms-3" href="admin/" target="_blank">
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()">
<i-bs class="me-1" name="airplane"></i-bs>&nbsp;<ng-container i18n>Start tour</ng-container>
</button>
<button class="btn btn-sm btn-outline-primary position-relative ms-md-5 me-1" (click)="showSystemStatus()"
[disabled]="!systemStatus"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }">
@if (!systemStatus) {
<div class="spinner-border spinner-border-sm me-1 h-75" role="status"></div>
} @else {
<i-bs class="me-2" name="card-checklist"></i-bs>
@if (systemStatusHasErrors) {
<span class="badge bg-body position-absolute top-0 start-100 translate-middle rounded-pill p-0">
<i-bs name="exclamation-circle-fill" class="text-danger" width="1.75em" height="1.75em"></i-bs>
</span>
} @else {
<span class="badge bg-body position-absolute top-0 start-100 translate-middle rounded-pill p-0">
<i-bs name="check-circle-fill" class="text-primary" width="1.75em" height="1.75em"></i-bs>
</span>
}
}
<ng-container i18n>System Status</ng-container>
</button>
<a *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" class="btn btn-sm btn-primary" href="admin/" target="_blank">
<ng-container i18n>Open Django Admin</ng-container>
<i-bs name="arrow-up-right"></i-bs>
&nbsp;<i-bs name="arrow-up-right"></i-bs>
</a>
</pngx-page-header>
@@ -351,5 +372,5 @@
<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" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
<button type="submit" class="btn btn-primary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
</form>
@@ -9,6 +9,8 @@ import {
NgbModule,
NgbAlertModule,
NgbNavLink,
NgbModal,
NgbModalModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { of, throwError } from 'rxjs'
@@ -39,6 +41,13 @@ import { SettingsComponent } from './settings.component'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
import { SystemStatusService } from 'src/app/services/system-status.service'
import {
SystemStatus,
InstallType,
SystemStatusItemStatus,
} from 'src/app/data/system-status'
const savedViews = [
{ id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true },
@@ -65,6 +74,8 @@ describe('SettingsComponent', () => {
let userService: UserService
let permissionsService: PermissionsService
let groupService: GroupService
let modalService: NgbModal
let systemStatusService: SystemStatusService
beforeEach(async () => {
TestBed.configureTestingModule({
@@ -96,6 +107,7 @@ describe('SettingsComponent', () => {
NgbAlertModule,
NgSelectModule,
NgxBootstrapIconsModule.pick(allIcons),
NgbModalModule,
],
}).compileComponents()
@@ -107,6 +119,8 @@ describe('SettingsComponent', () => {
settingsService.currentUser = users[0]
userService = TestBed.inject(UserService)
permissionsService = TestBed.inject(PermissionsService)
modalService = TestBed.inject(NgbModal)
systemStatusService = TestBed.inject(SystemStatusService)
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
@@ -372,4 +386,54 @@ describe('SettingsComponent', () => {
fixture.detectChanges()
expect(toastErrorSpy).toBeCalled()
})
it('should load system status on initialize, show errors if needed', () => {
const status: SystemStatus = {
pngx_version: '2.4.3',
server_os: 'macOS-14.1.1-arm64-arm-64bit',
install_type: InstallType.BareMetal,
storage: { total: 494384795648, available: 13573525504 },
database: {
type: 'sqlite',
url: '/paperless-ngx/data/db.sqlite3',
status: SystemStatusItemStatus.ERROR,
error: null,
migration_status: {
latest_migration: 'socialaccount.0006_alter_socialaccount_extra_data',
unapplied_migrations: [],
},
},
tasks: {
redis_url: 'redis://localhost:6379',
redis_status: SystemStatusItemStatus.ERROR,
redis_error:
'Error 61 connecting to localhost:6379. Connection refused.',
celery_status: SystemStatusItemStatus.ERROR,
index_status: SystemStatusItemStatus.OK,
index_last_modified: new Date().toISOString(),
index_error: null,
classifier_status: SystemStatusItemStatus.OK,
classifier_last_trained: new Date().toISOString(),
classifier_error: null,
},
}
jest.spyOn(systemStatusService, 'get').mockReturnValue(of(status))
completeSetup()
expect(component['systemStatus']).toEqual(status) // private
expect(component.systemStatusHasErrors).toBeTruthy()
// coverage
component['systemStatus'].database.status = SystemStatusItemStatus.OK
component['systemStatus'].tasks.redis_status = SystemStatusItemStatus.OK
component['systemStatus'].tasks.celery_status = SystemStatusItemStatus.OK
expect(component.systemStatusHasErrors).toBeFalsy()
})
it('should open system status dialog', () => {
const modalOpenSpy = jest.spyOn(modalService, 'open')
completeSetup()
component.showSystemStatus()
expect(modalOpenSpy).toHaveBeenCalledWith(SystemStatusDialogComponent, {
size: 'xl',
})
})
})
@@ -9,7 +9,11 @@ import {
} from '@angular/core'
import { FormGroup, FormControl } from '@angular/forms'
import { ActivatedRoute, Router } from '@angular/router'
import { NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
import {
NgbModal,
NgbModalRef,
NgbNavChangeEvent,
} from '@ng-bootstrap/ng-bootstrap'
import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import {
@@ -40,6 +44,12 @@ import {
} from 'src/app/services/settings.service'
import { ToastService, Toast } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
import { SystemStatusService } from 'src/app/services/system-status.service'
import {
SystemStatusItemStatus,
SystemStatus,
} from 'src/app/data/system-status'
enum SettingsNavIDs {
General = 1,
@@ -111,6 +121,18 @@ export class SettingsComponent
users: User[]
groups: Group[]
private systemStatus: SystemStatus
get systemStatusHasErrors(): boolean {
return (
this.systemStatus.database.status === SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.redis_status === SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.celery_status === SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.index_status === SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.classifier_status === SystemStatusItemStatus.ERROR
)
}
get computedDateLocale(): string {
return (
this.settingsForm.value.dateLocale ||
@@ -131,7 +153,9 @@ export class SettingsComponent
private usersService: UserService,
private groupsService: GroupService,
private router: Router,
public permissionsService: PermissionsService
public permissionsService: PermissionsService,
private modalService: NgbModal,
private systemStatusService: SystemStatusService
) {
super()
this.settings.settingsSaved.subscribe(() => {
@@ -360,6 +384,17 @@ export class SettingsComponent
// prevents loss of unsaved changes
this.settingsForm.patchValue(currentFormValue)
}
if (
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Admin
)
) {
this.systemStatusService.get().subscribe((status) => {
this.systemStatus = status
})
}
}
private emptyGroup(group: FormGroup) {
@@ -565,4 +600,14 @@ export class SettingsComponent
clearThemeColor() {
this.settingsForm.get('themeColor').patchValue('')
}
showSystemStatus() {
const modal: NgbModalRef = this.modalService.open(
SystemStatusDialogComponent,
{
size: 'xl',
}
)
modal.componentInstance.status = this.systemStatus
}
}
@@ -13,7 +13,7 @@
d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z"
transform="translate(0 0)" />
</svg>
<div class="ms-3 d-inline-block" [class.d-md-none]="slimSidebarEnabled">
<div class="ms-2 ms-md-3 d-inline-block" [class.d-md-none]="slimSidebarEnabled">
@if (customAppTitle?.length) {
<div class="d-flex flex-column align-items-start">
<span class="title">{{customAppTitle}}</span>
@@ -27,12 +27,12 @@
<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"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<form (ngSubmit)="search()" class="form-inline flex-grow-1">
<i-bs style="top: .25em;" width="1em" height="1em" name="search"></i-bs>
<i-bs width="1em" height="1em" name="search"></i-bs>
<input class="form-control form-control-sm" type="text" placeholder="Search documents" aria-label="Search"
[formControl]="searchField" [ngbTypeahead]="searchAutoComplete" (keyup)="searchFieldKeyup($event)"
(selectItem)="itemSelected($event)" i18n-placeholder>
@if (!searchFieldEmpty) {
<button type="button" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0" (click)="resetSearchField()">
<button type="button" class="btn btn-link btn-sm ps-0 pe-1 position-absolute top-0 end-0" (click)="resetSearchField()">
<i-bs width="1em" height="1em" name="x"></i-bs>
</button>
}
@@ -55,7 +55,7 @@
<i-bs class="me-2" name="person"></i-bs>&nbsp;<ng-container i18n>My Profile</ng-container>
</button>
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }">
*pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }">
<i-bs class="me-2" name="gear"></i-bs><ng-container i18n>Settings</ng-container>
</a>
<a ngbDropdownItem class="nav-link d-flex" href="accounts/logout/" (click)="onLogout()">
@@ -227,7 +227,7 @@
<span i18n>Administration</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }"
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }"
tourAnchor="tour.settings">
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
@@ -256,10 +256,10 @@
ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="list-task"></i-bs><span>&nbsp;<ng-container i18n>File Tasks</ng-container>@if (tasksService.failedFileTasks.length > 0) {
<span><span class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></span>
<span><span class="badge bg-danger ms-2 d-inline">{{tasksService.failedFileTasks.length}}</span></span>
}</span>
@if (tasksService.failedFileTasks.length > 0 && slimSidebarEnabled) {
<span class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>
<span class="badge bg-danger position-absolute top-0 end-0 d-none d-md-block">{{tasksService.failedFileTasks.length}}</span>
}
</a>
</li>
@@ -262,9 +262,15 @@ main {
> i-bs {
position: absolute;
left: 0.6rem;
top: 0.5rem;
top: .35rem;
color: rgba(255, 255, 255, 0.6);
@media screen and (min-width: 768px) {
// adjust for smaller font size on non-mobile
top: 0.25rem;
}
}
}
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -1,6 +1,9 @@
<form [formGroup]="objectForm" (ngSubmit)="save()" autocomplete="off">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{getTitle()}}</h4>
@if (object?.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{object.id}}</span>
}
<button type="button" [disabled]="!closeEnabled" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
@@ -35,7 +38,7 @@
<div ngbAccordionItem>
<div ngbAccordionHeader>
<button ngbAccordionButton>{{i + 1}}. {{getTriggerTypeOptionName(triggerFields.controls[i].value.type)}}
@if(trigger.id > -1) {
@if(trigger.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{trigger.id}}</span>
}
<pngx-confirm-button
@@ -77,7 +80,7 @@
<div ngbAccordionItem cdkDrag [formGroup]="actionFields.controls[i]">
<div ngbAccordionHeader>
<button ngbAccordionButton>{{i + 1}}. {{getActionTypeOptionName(actionFields.controls[i].value.type)}}
@if(action.id > -1) {
@if(action.id) {
<span class="badge bg-primary text-primary-text-contrast ms-2">ID: {{action.id}}</span>
}
<pngx-confirm-button
@@ -91,63 +94,7 @@
</div>
<div ngbAccordionCollapse>
<div ngbAccordionBody>
<pngx-input-select i18n-title title="Action type" [horizontal]="true" [items]="actionTypeOptions" formControlName="type"></pngx-input-select>
<input type="hidden" formControlName="id" />
<div class="row">
<div class="col">
<pngx-input-text i18n-title title="Assign title" formControlName="assign_title" i18n-hint hint="Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>." [error]="error?.actions?.[i]?.assign_title"></pngx-input-text>
<pngx-input-tags [allowCreate]="false" i18n-title title="Assign tags" formControlName="assign_tags"></pngx-input-tags>
<pngx-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
</div>
<div class="col">
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
<div>
<label class="form-label" i18n>Assign view permissions</label>
<div class="mb-2">
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="view" formControlName="assign_view_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="view" formControlName="assign_view_groups"></pngx-permissions-group>
</div>
</div>
</div>
<label class="form-label" i18n>Assign edit permissions</label>
<div>
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="change" formControlName="assign_change_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="change" formControlName="assign_change_groups"></pngx-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>
</div>
<ng-template [ngTemplateOutlet]="actionForm" [ngTemplateOutletContext]="{ formGroup: actionFields.controls[i], action: action }"></ng-template>
</div>
</div>
</div>
@@ -201,3 +148,154 @@
</div>
</div>
</ng-template>
<ng-template #actionForm let-formGroup="formGroup" let action="action">
<div [formGroup]="formGroup">
<input type="hidden" formControlName="id" />
<pngx-input-select i18n-title title="Action type" [horizontal]="true" [items]="actionTypeOptions" formControlName="type"></pngx-input-select>
@switch(formGroup.get('type').value) {
@case ( WorkflowActionType.Assignment) {
<div class="row">
<div class="col">
<pngx-input-text i18n-title title="Assign title" formControlName="assign_title" i18n-hint hint="Can include some placeholders, see <a target='_blank' href='https://docs.paperless-ngx.com/usage/#workflows'>documentation</a>." [error]="error?.actions?.[i]?.assign_title"></pngx-input-text>
<pngx-input-tags [allowCreate]="false" i18n-title title="Assign tags" formControlName="assign_tags"></pngx-input-tags>
<pngx-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
<pngx-input-select i18n-title title="Assign storage path" [items]="storagePaths" [allowNull]="true" formControlName="assign_storage_path"></pngx-input-select>
<pngx-input-select i18n-title title="Assign custom fields" multiple="true" [items]="customFields" [allowNull]="true" formControlName="assign_custom_fields"></pngx-input-select>
</div>
<div class="col">
<pngx-input-select i18n-title title="Assign owner" [items]="users" bindLabel="username" formControlName="assign_owner" [allowNull]="true"></pngx-input-select>
<div>
<label class="form-label" i18n>Assign view permissions</label>
<div class="mb-2">
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="view" formControlName="assign_view_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="view" formControlName="assign_view_groups"></pngx-permissions-group>
</div>
</div>
</div>
<label class="form-label" i18n>Assign edit permissions</label>
<div>
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="change" formControlName="assign_change_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="change" formControlName="assign_change_groups"></pngx-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>
</div>
}
@case (WorkflowActionType.Removal) {
<div class="row">
<div class="col">
<h6 class="form-label" i18n>Remove tags</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_tags"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-tags [allowCreate]="false" title="" formControlName="remove_tags"></pngx-input-tags>
</div>
<h6 class="form-label" i18n>Remove correspondents</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_correspondents"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="correspondents" formControlName="remove_correspondents"></pngx-input-select>
</div>
<h6 class="form-label" i18n>Remove document types</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_document_types"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="documentTypes" formControlName="remove_document_types"></pngx-input-select>
</div>
<h6 class="form-label" i18n>Remove storage paths</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_storage_paths"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="storagePaths" formControlName="remove_storage_paths"></pngx-input-select>
</div>
<h6 class="form-label" i18n>Remove custom fields</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_custom_fields"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="customFields" formControlName="remove_custom_fields"></pngx-input-select>
</div>
</div>
<div class="col">
<h6 class="form-label" i18n>Remove owners</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_owners"></pngx-input-switch>
<div class="mt-n3">
<pngx-input-select i18n-title title="" multiple="true" [items]="users" bindLabel="username" formControlName="remove_owners"></pngx-input-select>
</div>
<h6 class="form-label" i18n>Remove permissions</h6>
<pngx-input-switch i18n-title title="Remove all" [horizontal]="true" formControlName="remove_all_permissions"></pngx-input-switch>
<div>
<label class="form-label" i18n>View permissions</label>
<div class="mb-2">
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="view" formControlName="remove_view_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="view" formControlName="remove_view_groups"></pngx-permissions-group>
</div>
</div>
</div>
<label class="form-label" i18n>Edit permissions</label>
<div>
<div class="row mb-1">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Users:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-user type="change" formControlName="remove_change_users"></pngx-permissions-user>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<label class="form-label d-block my-2 text-nowrap" i18n>Groups:</label>
</div>
<div class="col-lg-9">
<pngx-permissions-group type="change" formControlName="remove_change_groups"></pngx-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>
</div>
}
}
</div>
</ng-template>
@@ -235,4 +235,103 @@ describe('WorkflowEditDialogComponent', () => {
MATCHING_ALGORITHMS.find((a) => a.id === MATCH_AUTO)
)
})
it('should disable or enable action fields based on removal action type', () => {
const workflow: Workflow = {
name: 'Workflow 1',
id: 1,
order: 1,
enabled: true,
triggers: [],
actions: [
{
id: 1,
type: WorkflowActionType.Removal,
remove_all_tags: true,
remove_all_document_types: true,
remove_all_correspondents: true,
remove_all_storage_paths: true,
remove_all_custom_fields: true,
remove_all_owners: true,
remove_all_permissions: true,
},
],
}
component.object = workflow
component.ngOnInit()
component['checkRemovalActionFields'](workflow)
// Assert that the action fields are disabled or enabled correctly
expect(
component.actionFields.at(0).get('remove_tags').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_document_types').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_correspondents').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_storage_paths').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_custom_fields').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_owners').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_view_users').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_view_groups').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_change_users').disabled
).toBeTruthy()
expect(
component.actionFields.at(0).get('remove_change_groups').disabled
).toBeTruthy()
workflow.actions[0].remove_all_tags = false
workflow.actions[0].remove_all_document_types = false
workflow.actions[0].remove_all_correspondents = false
workflow.actions[0].remove_all_storage_paths = false
workflow.actions[0].remove_all_custom_fields = false
workflow.actions[0].remove_all_owners = false
workflow.actions[0].remove_all_permissions = false
component['checkRemovalActionFields'](workflow)
// Assert that the action fields are disabled or enabled correctly
expect(component.actionFields.at(0).get('remove_tags').disabled).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_document_types').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_correspondents').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_storage_paths').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_custom_fields').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_owners').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_view_users').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_view_groups').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_change_users').disabled
).toBeFalsy()
expect(
component.actionFields.at(0).get('remove_change_groups').disabled
).toBeFalsy()
})
})
@@ -68,6 +68,10 @@ export const WORKFLOW_ACTION_OPTIONS = [
id: WorkflowActionType.Assignment,
name: $localize`Assignment`,
},
{
id: WorkflowActionType.Removal,
name: $localize`Removal`,
},
]
const TRIGGER_MATCHING_ALGORITHMS = MATCHING_ALGORITHMS.filter(
@@ -84,6 +88,7 @@ export class WorkflowEditDialogComponent
implements OnInit
{
public WorkflowTriggerType = WorkflowTriggerType
public WorkflowActionType = WorkflowActionType
templates: Workflow[]
correspondents: Correspondent[]
@@ -159,6 +164,124 @@ export class WorkflowEditDialogComponent
ngOnInit(): void {
super.ngOnInit()
this.updateAllTriggerActionFields()
this.objectForm.valueChanges.subscribe(
this.checkRemovalActionFields.bind(this)
)
this.checkRemovalActionFields(this.objectForm.value)
}
private checkRemovalActionFields(formWorkflow: Workflow) {
formWorkflow.actions
.filter((action) => action.type === WorkflowActionType.Removal)
.forEach((action, i) => {
if (action.remove_all_tags) {
this.actionFields
.at(i)
.get('remove_tags')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_tags')
.enable({ emitEvent: false })
}
if (action.remove_all_document_types) {
this.actionFields
.at(i)
.get('remove_document_types')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_document_types')
.enable({ emitEvent: false })
}
if (action.remove_all_correspondents) {
this.actionFields
.at(i)
.get('remove_correspondents')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_correspondents')
.enable({ emitEvent: false })
}
if (action.remove_all_storage_paths) {
this.actionFields
.at(i)
.get('remove_storage_paths')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_storage_paths')
.enable({ emitEvent: false })
}
if (action.remove_all_custom_fields) {
this.actionFields
.at(i)
.get('remove_custom_fields')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_custom_fields')
.enable({ emitEvent: false })
}
if (action.remove_all_owners) {
this.actionFields
.at(i)
.get('remove_owners')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_owners')
.enable({ emitEvent: false })
}
if (action.remove_all_permissions) {
this.actionFields
.at(i)
.get('remove_view_users')
.disable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_view_groups')
.disable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_change_users')
.disable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_change_groups')
.disable({ emitEvent: false })
} else {
this.actionFields
.at(i)
.get('remove_view_users')
.enable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_view_groups')
.enable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_change_users')
.enable({ emitEvent: false })
this.actionFields
.at(i)
.get('remove_change_groups')
.enable({ emitEvent: false })
}
})
}
get triggerFields(): FormArray {
@@ -215,6 +338,31 @@ export class WorkflowEditDialogComponent
assign_change_users: new FormControl(action.assign_change_users),
assign_change_groups: new FormControl(action.assign_change_groups),
assign_custom_fields: new FormControl(action.assign_custom_fields),
remove_tags: new FormControl(action.remove_tags),
remove_all_tags: new FormControl(action.remove_all_tags),
remove_document_types: new FormControl(action.remove_document_types),
remove_all_document_types: new FormControl(
action.remove_all_document_types
),
remove_correspondents: new FormControl(action.remove_correspondents),
remove_all_correspondents: new FormControl(
action.remove_all_correspondents
),
remove_storage_paths: new FormControl(action.remove_storage_paths),
remove_all_storage_paths: new FormControl(
action.remove_all_storage_paths
),
remove_owners: new FormControl(action.remove_owners),
remove_all_owners: new FormControl(action.remove_all_owners),
remove_view_users: new FormControl(action.remove_view_users),
remove_view_groups: new FormControl(action.remove_view_groups),
remove_change_users: new FormControl(action.remove_change_users),
remove_change_groups: new FormControl(action.remove_change_groups),
remove_all_permissions: new FormControl(action.remove_all_permissions),
remove_custom_fields: new FormControl(action.remove_custom_fields),
remove_all_custom_fields: new FormControl(
action.remove_all_custom_fields
),
}),
{ emitEvent }
)
@@ -290,6 +438,23 @@ export class WorkflowEditDialogComponent
assign_change_users: [],
assign_change_groups: [],
assign_custom_fields: [],
remove_tags: [],
remove_all_tags: false,
remove_document_types: [],
remove_all_document_types: false,
remove_correspondents: [],
remove_all_correspondents: false,
remove_storage_paths: [],
remove_all_storage_paths: false,
remove_owners: [],
remove_all_owners: false,
remove_view_users: [],
remove_view_groups: [],
remove_change_users: [],
remove_change_groups: [],
remove_all_permissions: false,
remove_custom_fields: [],
remove_all_custom_fields: false,
}
this.object.actions.push(action)
this.createActionField(action)
@@ -0,0 +1,27 @@
<div class="mb-3" [class.pb-3]="error">
<div class="row">
<div class="d-flex align-items-center position-relative hidden-button-container" [class.col-md-3]="horizontal">
@if (title) {
<label class="form-label" [class.mb-md-0]="horizontal" [for]="inputId">{{title}}</label>
}
@if (removable) {
<button type="button" class="btn btn-sm btn-danger position-absolute left-0" (click)="removed.emit(this)">
<i-bs name="x"></i-bs>&nbsp;<ng-container i18n>Remove</ng-container>
</button>
}
</div>
<div class="position-relative" [class.col-md-9]="horizontal">
<div class="input-group" [class.is-invalid]="error">
<span class="input-group-text fw-bold bg-light">{{monetaryValue | currency: currencyCode }}</span>
<input #currencyField class="form-control text-muted mw-60" tabindex="0" [(ngModel)]="currencyCode" maxlength="3" [class.is-invalid]="error" (change)="onChange(value)" [disabled]="disabled">
<input #inputField type="number" tabindex="0" class="form-control text-muted" step=".01" [id]="inputId" [(ngModel)]="monetaryValue" (change)="onChange(value)" [class.is-invalid]="error" [disabled]="disabled">
</div>
<div class="invalid-feedback position-absolute top-100">
{{error}}
</div>
@if (hint) {
<small class="form-text text-muted">{{hint}}</small>
}
</div>
</div>
</div>
@@ -0,0 +1,11 @@
.input-group-text {
font-size: inherit;
}
.text-muted:focus-within {
color: var(--bs-body-color) !important;
}
.mw-60 {
max-width: 60px;
}
@@ -0,0 +1,59 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import {
FormsModule,
NG_VALUE_ACCESSOR,
ReactiveFormsModule,
} from '@angular/forms'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { CurrencyPipe } from '@angular/common'
import { MonetaryComponent } from './monetary.component'
describe('MonetaryComponent', () => {
let component: MonetaryComponent
let fixture: ComponentFixture<MonetaryComponent>
let input: HTMLInputElement
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MonetaryComponent],
providers: [CurrencyPipe],
imports: [FormsModule, ReactiveFormsModule, HttpClientTestingModule],
}).compileComponents()
fixture = TestBed.createComponent(MonetaryComponent)
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
component = fixture.componentInstance
fixture.detectChanges()
input = component.inputField.nativeElement
})
it('should set the currency code correctly', () => {
expect(component.currencyCode).toEqual('USD') // default
component.currencyCode = 'EUR'
expect(component.currencyCode).toEqual('EUR')
component.value = 'G123.4'
jest
.spyOn(document, 'activeElement', 'get')
.mockReturnValue(component.currencyField.nativeElement)
expect(component.currencyCode).toEqual('G')
})
it('should parse monetary value only when out of focus', () => {
component.monetaryValue = 10.5
jest.spyOn(document, 'activeElement', 'get').mockReturnValue(null)
expect(component.monetaryValue).toEqual('10.50')
component.value = 'GBP123.4'
jest
.spyOn(document, 'activeElement', 'get')
.mockReturnValue(component.inputField.nativeElement)
expect(component.monetaryValue).toEqual('123.4')
})
it('should report value including currency code and monetary value', () => {
component.currencyCode = 'EUR'
component.monetaryValue = 10.5
expect(component.value).toEqual('EUR10.50')
})
})
@@ -0,0 +1,59 @@
import {
Component,
DEFAULT_CURRENCY_CODE,
ElementRef,
forwardRef,
Inject,
ViewChild,
} from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { AbstractInputComponent } from '../abstract-input'
@Component({
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MonetaryComponent),
multi: true,
},
],
selector: 'pngx-input-monetary',
templateUrl: './monetary.component.html',
styleUrls: ['./monetary.component.scss'],
})
export class MonetaryComponent extends AbstractInputComponent<string> {
@ViewChild('currencyField')
currencyField: ElementRef
constructor(
@Inject(DEFAULT_CURRENCY_CODE) public defaultCurrencyCode: string
) {
super()
}
get currencyCode(): string {
const focused = document.activeElement === this.currencyField?.nativeElement
if (focused && this.value) return this.value.match(/^([A-Z]{0,3})/)?.[0]
return (
this.value
?.toString()
.toUpperCase()
.match(/^([A-Z]{1,3})/)?.[0] ?? this.defaultCurrencyCode
)
}
set currencyCode(value: string) {
this.value = value + this.monetaryValue?.toString()
}
get monetaryValue(): string {
if (!this.value) return null
const focused = document.activeElement === this.inputField?.nativeElement
const val = parseFloat(this.value.toString().replace(/[^0-9.,]+/g, ''))
return focused ? val.toString() : val.toFixed(2)
}
set monetaryValue(value: number) {
this.value = this.currencyCode + value.toFixed(2)
}
}
@@ -1,4 +1,4 @@
<div class="paperless-input-select">
<div class="paperless-input-select" [class.disabled]="disabled">
<div>
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"
@@ -0,0 +1,11 @@
.paperless-input-select.disabled {
cursor: not-allowed;
::ng-deep ng-select {
pointer-events: none;
.ng-select-container {
background-color: var(--pngx-bg-alt) !important;
}
}
}
@@ -1,4 +1,4 @@
<div class="paperless-input-select">
<div class="paperless-input-select" [class.disabled]="disabled">
<div>
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"
@@ -0,0 +1,11 @@
.paperless-input-select.disabled {
cursor: not-allowed;
::ng-deep ng-select {
pointer-events: none;
.ng-select-container {
background-color: var(--pngx-bg-alt) !important;
}
}
}
@@ -1,6 +1,7 @@
// styles for ng-select child are in styles.scss
.paperless-input-select.disabled {
.input-group {
.input-group,
div > div {
cursor: not-allowed;
}
@@ -19,7 +19,7 @@
}
</h3>
</div>
<div class="btn-toolbar col col-md-auto">
<div class="btn-toolbar col col-md-auto gap-2">
<ng-content></ng-content>
</div>
</div>
@@ -0,0 +1,154 @@
<div class="modal-header">
<h5 class="modal-title" id="modal-basic-title" i18n>System Status</h5>
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
</div>
<div class="modal-body">
@if (!status) {
<div class="w-100 h-100 d-flex align-items-center justify-content-center">
<div>
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</div>
</div>
} @else {
<div class="row row-cols-1 row-cols-md-3 g-3">
<div class="col">
<div class="card bg-light h-100">
<div class="card-header">
<h5 class="card-title mb-0" i18n>Environment</h5>
</div>
<div class="card-body">
<dl class="card-text">
<dt i18n>Paperless-ngx Version</dt>
<dd>{{status.pngx_version}}</dd>
<dt i18n>Install Type</dt>
<dd>{{status.install_type}}</dd>
<dt i18n>Server OS</dt>
<dd>{{status.server_os}}</dd>
<dt i18n>Media Storage</dt>
<dd>
<ngb-progressbar style="height: 4px;" class="mt-2 mb-1" type="primary" [max]="status.storage.total" [value]="status.storage.total - status.storage.available"></ngb-progressbar>
<span class="small">{{status.storage.available | filesize}} <ng-container i18n>available</ng-container> ({{status.storage.total | filesize}} <ng-container i18n>total</ng-container>)</span>
</dd>
</dl>
</div>
</div>
</div>
<div class="col">
<div class="card bg-light h-100">
<div class="card-header">
<h5 class="card-title mb-0" i18n>Database</h5>
</div>
<div class="card-body">
<dl class="card-text">
<dt i18n>Type</dt>
<dd>{{status.database.type}}</dd>
<dt i18n>Status</dt>
<dd class="d-flex align-items-center">
{{status.database.status}}
@if (status.database.status === 'OK') {
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1" ngbPopover="{{status.database.url}}" triggers="mouseenter:mouseleave"></i-bs>
} @else {
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1" ngbPopover="{{status.database.url}}: {{status.database.error}}" triggers="mouseenter:mouseleave"></i-bs>
}
</dd>
<dt i18n>Migration Status</dt>
<dd class="d-flex align-items-center">
@if (status.database.migration_status.unapplied_migrations.length === 0) {
<ng-container>Up to date</ng-container><i-bs name="check-circle-fill" class="text-primary ms-2 lh-1" [ngbPopover]="migrationStatus" triggers="mouseenter:mouseleave"></i-bs>
} @else {
<ng-container>{{status.database.migration_status.unapplied_migrations.length}} Pending</ng-container><i-bs name="exclamation-triangle-fill" class="text-warning ms-2 lh-1" [ngbPopover]="migrationStatus" triggers="mouseenter:mouseleave"></i-bs>
}
<ng-template #migrationStatus>
<h6><ng-container i18n>Latest Migration</ng-container>:</h6> <span class="font-monospace small">{{status.database.migration_status.latest_migration}}</span>
@if (status.database.migration_status.unapplied_migrations.length > 0) {
<h6 class="mt-3"><ng-container i18n>Pending Migrations</ng-container>:</h6>
<ul>
@for (migration of status.database.migration_status.unapplied_migrations; track migration) {
<li class="font-monospace small">{{migration}}</li>
}
</ul>
}
</ng-template>
</dd>
</dl>
</div>
</div>
</div>
<div class="col">
<div class="card bg-light h-100">
<div class="card-header">
<h5 class="card-title mb-0" i18n>Tasks</h5>
</div>
<div class="card-body">
<dl class="card-text">
<dt i18n>Redis Status</dt>
<dd class="d-flex align-items-center">
{{status.tasks.redis_status}}
@if (status.tasks.redis_status === 'OK') {
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1" ngbPopover="{{status.tasks.redis_url}}" triggers="mouseenter:mouseleave"></i-bs>
} @else {
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1" ngbPopover="{{status.tasks.redis_url}}: {{status.tasks.redis_error}}" triggers="mouseenter:mouseleave"></i-bs>
}
</dd>
<dt i18n>Celery Status</dt>
<dd class="d-flex align-items-center">
{{status.tasks.celery_status}}
@if (status.tasks.celery_status === 'OK') {
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
} @else {
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1"></i-bs>
}
</dd>
<dt i18n>Search Index</dt>
<dd class="d-flex align-items-center">
{{status.tasks.index_status}}
@if (status.tasks.index_status === 'OK') {
@if (isStale(status.tasks.index_last_modified)) {
<i-bs name="exclamation-triangle-fill" class="text-warning ms-2 lh-1" [ngbPopover]="indexStatus" triggers="mouseenter:mouseleave"></i-bs>
} @else {
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1" [ngbPopover]="indexStatus" triggers="mouseenter:mouseleave"></i-bs>
}
} @else {
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1" ngbPopover="{{status.tasks.index_error}}" triggers="mouseenter:mouseleave"></i-bs>
}
</dd>
<ng-template #indexStatus>
<h6><ng-container i18n>Last Updated</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.index_last_modified | customDate:'medium'}}</span>
</ng-template>
<dt i18n>Classifier</dt>
<dd class="d-flex align-items-center">
{{status.tasks.classifier_status}}
@if (status.tasks.classifier_status === 'OK') {
@if (isStale(status.tasks.classifier_last_trained)) {
<i-bs name="exclamation-triangle-fill" class="text-warning ms-2 lh-1" [ngbPopover]="classifierStatus" triggers="mouseenter:mouseleave"></i-bs>
} @else {
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1" [ngbPopover]="classifierStatus" triggers="mouseenter:mouseleave"></i-bs>
}
} @else {
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1" ngbPopover="{{status.tasks.classifier_error}}" triggers="mouseenter:mouseleave"></i-bs>
}
</dd>
<ng-template #classifierStatus>
<h6><ng-container i18n>Last Trained</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.classifier_last_trained | customDate:'medium'}}</span>
</ng-template>
</dl>
</div>
</div>
</div>
</div>
}
</div>
<div class="modal-footer">
<button class="btn btn-sm btn-outline-secondary" (click)="copy()">
@if (!copied) {
<i-bs name="clipboard-fill"></i-bs>&nbsp;
}
@if (copied) {
<i-bs name="clipboard-check-fill"></i-bs>&nbsp;
}
<ng-container i18n>Copy</ng-container>
</button>
</div>
@@ -0,0 +1,103 @@
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import {
NgbActiveModal,
NgbModalModule,
NgbPopoverModule,
NgbProgressbarModule,
} from '@ng-bootstrap/ng-bootstrap'
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
import { SystemStatusDialogComponent } from './system-status-dialog.component'
import {
SystemStatusItemStatus,
InstallType,
SystemStatus,
} from 'src/app/data/system-status'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { NgxFilesizeModule } from 'ngx-filesize'
const status: SystemStatus = {
pngx_version: '2.4.3',
server_os: 'macOS-14.1.1-arm64-arm-64bit',
install_type: InstallType.BareMetal,
storage: { total: 494384795648, available: 13573525504 },
database: {
type: 'sqlite',
url: '/paperless-ngx/data/db.sqlite3',
status: SystemStatusItemStatus.ERROR,
error: null,
migration_status: {
latest_migration: 'socialaccount.0006_alter_socialaccount_extra_data',
unapplied_migrations: [],
},
},
tasks: {
redis_url: 'redis://localhost:6379',
redis_status: SystemStatusItemStatus.ERROR,
redis_error: 'Error 61 connecting to localhost:6379. Connection refused.',
celery_status: SystemStatusItemStatus.ERROR,
index_status: SystemStatusItemStatus.OK,
index_last_modified: new Date().toISOString(),
index_error: null,
classifier_status: SystemStatusItemStatus.OK,
classifier_last_trained: new Date().toISOString(),
classifier_error: null,
},
}
describe('SystemStatusDialogComponent', () => {
let component: SystemStatusDialogComponent
let fixture: ComponentFixture<SystemStatusDialogComponent>
let clipboard: Clipboard
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SystemStatusDialogComponent],
providers: [NgbActiveModal],
imports: [
NgbModalModule,
ClipboardModule,
HttpClientTestingModule,
NgxBootstrapIconsModule.pick(allIcons),
NgxFilesizeModule,
NgbPopoverModule,
NgbProgressbarModule,
],
}).compileComponents()
fixture = TestBed.createComponent(SystemStatusDialogComponent)
component = fixture.componentInstance
component.status = status
clipboard = TestBed.inject(Clipboard)
fixture.detectChanges()
})
it('should close the active modal', () => {
const closeSpy = jest.spyOn(component.activeModal, 'close')
component.close()
expect(closeSpy).toHaveBeenCalled()
})
it('should copy the system status to clipboard', fakeAsync(() => {
jest.spyOn(clipboard, 'copy')
component.copy()
expect(clipboard.copy).toHaveBeenCalledWith(
JSON.stringify(component.status)
)
expect(component.copied).toBeTruthy()
tick(3000)
expect(component.copied).toBeFalsy()
}))
it('should calculate if date is stale', () => {
const date = new Date()
date.setHours(date.getHours() - 25)
expect(component.isStale(date.toISOString())).toBeTruthy()
expect(component.isStale(date.toISOString(), 26)).toBeFalsy()
})
})
@@ -0,0 +1,39 @@
import { Component, Input } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { SystemStatus } from 'src/app/data/system-status'
import { SystemStatusService } from 'src/app/services/system-status.service'
import { Clipboard } from '@angular/cdk/clipboard'
@Component({
selector: 'pngx-system-status-dialog',
templateUrl: './system-status-dialog.component.html',
styleUrl: './system-status-dialog.component.scss',
})
export class SystemStatusDialogComponent {
public status: SystemStatus
public copied: boolean = false
constructor(
public activeModal: NgbActiveModal,
private clipboard: Clipboard
) {}
public close() {
this.activeModal.close()
}
public copy() {
this.clipboard.copy(JSON.stringify(this.status))
this.copied = true
setTimeout(() => {
this.copied = false
}, 3000)
}
public isStale(dateStr: string, hours: number = 24): boolean {
const date = new Date(dateStr)
const now = new Date()
return now.getTime() - date.getTime() > hours * 60 * 60 * 1000
}
}
@@ -1,11 +1,11 @@
<pngx-page-header [(title)]="title">
@if (contentRenderType === ContentRenderType.PDF && !useNativePdfViewer) {
<div class="input-group input-group-sm me-2 d-none d-md-flex">
<div class="input-group input-group-sm d-none d-md-flex">
<div class="input-group-text" i18n>Page</div>
<input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages" [(ngModel)]="previewCurrentPage" />
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
</div>
<div class="input-group input-group-sm me-5 d-none d-md-flex">
<div class="input-group input-group-sm me-md-5 d-none d-md-flex">
<button class="btn btn-outline-secondary" (click)="decreaseZoom()" i18n>-</button>
<select class="form-select" (change)="onZoomSelect($event)">
@for (setting of zoomSettings; track setting) {
@@ -18,11 +18,11 @@
</div>
}
<button type="button" class="btn btn-sm btn-outline-danger me-4" (click)="delete()" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
<button type="button" class="btn btn-sm btn-outline-danger me-md-4" (click)="delete()" [disabled]="!userIsOwner" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
<i-bs width="1.2em" height="1.2em" name="trash"></i-bs><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
</button>
<div class="btn-group me-2">
<div class="btn-group">
<a [href]="downloadUrl" class="btn btn-sm btn-outline-primary">
<i-bs width="1.2em" height="1.2em" name="download"></i-bs><span class="d-none d-lg-inline ps-1" i18n>Download</span>
</a>
@@ -38,7 +38,7 @@
</div>
<div class="ms-auto" ngbDropdown>
<button class="btn btn-sm btn-outline-primary me-2" id="actionsDropdown" ngbDropdownToggle>
<button class="btn btn-sm btn-outline-primary" id="actionsDropdown" ngbDropdownToggle>
<i-bs name="three-dots"></i-bs>
<div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Actions</ng-container></div>
</button>
@@ -55,7 +55,6 @@
<pngx-custom-fields-dropdown
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }"
class="me-2"
[documentId]="documentId"
[disabled]="!userIsOwner"
[existingFields]="document?.custom_fields"
@@ -142,14 +141,12 @@
[error]="getCustomFieldError(i)"></pngx-input-number>
}
@case (CustomFieldDataType.Monetary) {
<pngx-input-number formControlName="value"
<pngx-input-monetary formControlName="value"
[title]="getCustomFieldFromInstance(fieldInstance)?.name"
[removable]="userIsOwner"
(removed)="removeField(fieldInstance)"
[horizontal]="true"
[showAdd]="false"
[step]=".01"
[error]="getCustomFieldError(i)"></pngx-input-number>
[error]="getCustomFieldError(i)"></pngx-input-monetary>
}
@case (CustomFieldDataType.Boolean) {
<pngx-input-check formControlName="value"
@@ -1,6 +1,6 @@
<pngx-page-header [title]="getTitle()">
<div ngbDropdown class="me-2 d-flex">
<div ngbDropdown class="d-flex">
<button class="btn btn-sm btn-outline-primary" id="dropdownSelect" ngbDropdownToggle>
<i-bs name="text-indent-left"></i-bs>
<div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Select</ng-container></div>
@@ -26,7 +26,7 @@
</label>
</div>
<div ngbDropdown class="btn-group ms-2 flex-fill">
<div ngbDropdown class="btn-group flex-fill">
<button class="btn btn-outline-primary btn-sm" id="dropdownBasic1" ngbDropdownToggle i18n>Sort</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1" class="shadow dropdown-menu-right">
<div class="w-100 d-flex pb-2 mb-1 border-bottom">
@@ -49,7 +49,7 @@
</div>
</div>
<div class="btn-group ms-2 flex-fill" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }" ngbDropdown role="group">
<div class="btn-group flex-fill" *pngxIfPermissions="{ 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>
<ng-container i18n>Views</ng-container>
@if (savedViewIsModified) {
@@ -5,7 +5,7 @@
i18n-info
infoLink="usage/#custom-fields"
>
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editField()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.CustomField }">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="editField()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.CustomField }">
<i-bs name="plus-circle"></i-bs>&nbsp;<ng-container i18n>Add Field</ng-container>
</button>
</pngx-page-header>
@@ -1,120 +1,120 @@
<pngx-page-header title="{{ typeNamePlural | titlecase }}">
<button class="btn btn-sm btn-outline-secondary me-2" (click)="clearSelection()" [hidden]="selectedObjects.size === 0">
<button class="btn btn-sm btn-outline-secondary" (click)="clearSelection()" [hidden]="selectedObjects.size === 0">
<i-bs name="x"></i-bs>&nbsp;<ng-container i18n>Clear selection</ng-container>
</button>
<button type="button" class="btn btn-sm btn-outline-primary me-2" (click)="setPermissions()" [disabled]="!userCanBulkEdit(PermissionAction.Change) || selectedObjects.size === 0">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="setPermissions()" [disabled]="!userCanBulkEdit(PermissionAction.Change) || selectedObjects.size === 0">
<i-bs name="person-fill-lock"></i-bs>&nbsp;<ng-container i18n>Permissions</ng-container>
</button>
<button type="button" class="btn btn-sm btn-outline-danger me-5" (click)="delete()" [disabled]="!userCanBulkEdit(PermissionAction.Delete) || selectedObjects.size === 0">
<button type="button" class="btn btn-sm btn-outline-danger" (click)="delete()" [disabled]="!userCanBulkEdit(PermissionAction.Delete) || selectedObjects.size === 0">
<i-bs name="trash"></i-bs>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" *pngxIfPermissions="{ action: PermissionAction.Add, type: permissionType }">
<button type="button" class="btn btn-sm btn-outline-primary ms-md-5" (click)="openCreateDialog()" *pngxIfPermissions="{ action: PermissionAction.Add, type: permissionType }">
<i-bs name="plus-circle"></i-bs>&nbsp;<ng-container i18n>Create</ng-container>
</button>
</pngx-page-header>
</pngx-page-header>
<div class="row mb-3">
<div class="col-md mb-2 mb-xl-0">
<div class="form-inline d-flex align-items-center">
<label class="text-muted me-2 mb-0" i18n>Filter by:</label>
<input class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder>
</div>
</div>
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
<div class="row mb-3">
<div class="col-md mb-2 mb-xl-0">
<div class="form-inline d-flex align-items-center">
<label class="text-muted me-2 mb-0" i18n>Filter by:</label>
<input class="form-control form-control-sm flex-fill w-auto" type="text" autofocus [(ngModel)]="nameFilter" (keyup)="onNameFilterKeyUp($event)" placeholder="Name" i18n-placeholder>
</div>
</div>
<div class="card border mb-3">
<table class="table table-striped align-middle shadow-sm mb-0">
<thead>
<tr>
<th scope="col">
<div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="all-objects" [disabled]="data.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
<label class="form-check-label" for="all-objects"></label>
</div>
</th>
<th scope="col" class="fw-normal" pngxSortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
<th scope="col" class="fw-normal d-none d-sm-table-cell" pngxSortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
<th scope="col" class="fw-normal" pngxSortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
@for (column of extraColumns; track column) {
<th scope="col" class="fw-normal" pngxSortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
}
<th scope="col" class="fw-normal" i18n>Actions</th>
</tr>
</thead>
<tbody>
@if (isLoading) {
<tr>
<td colspan="5">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</td>
</tr>
}
@for (object of data; track object) {
<tr (click)="toggleSelected(object); $event.stopPropagation();">
<td>
<div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="{{typeName}}{{object.id}}" [checked]="selectedObjects.has(object.id)" (click)="toggleSelected(object); $event.stopPropagation();">
<label class="form-check-label" for="{{typeName}}{{object.id}}"></label>
</div>
</td>
<td scope="row"><button class="btn btn-link ms-0 ps-0 text-start" (click)="userCanEdit(object) ? openEditDialog(object) : null">{{ object.name }}</button> </td>
<td scope="row" class="d-none d-sm-table-cell">{{ getMatching(object) }}</td>
<td scope="row">{{ object.document_count }}</td>
@for (column of extraColumns; track column) {
<td scope="row">
@if (column.rendersHtml) {
<div [innerHtml]="column.valueFn.call(null, object) | safeHtml"></div>
} @else {
{{ column.valueFn.call(null, object) }}
}
</td>
}
<td scope="row">
<div class="btn-group d-block d-sm-none">
<div ngbDropdown class="d-inline-block">
<button type="button" class="btn btn-link" id="actionsMenuMobile" ngbDropdownToggle>
<i-bs name="three-dots-vertical"></i-bs>
</button>
<div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
<button (click)="filterDocuments(object)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" ngbDropdownItem i18n>Filter Documents</button>
<button (click)="openEditDialog(object)" *pngxIfPermissions="{ action: PermissionAction.Change, type: permissionType }" ngbDropdownItem i18n>Edit</button>
<button class="text-danger" (click)="openDeleteDialog(object)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" ngbDropdownItem i18n>Delete</button>
</div>
</div>
</div>
<div class="btn-group d-none d-sm-block">
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<i-bs width="1em" height="1em" name="filter"></i-bs>&nbsp;<ng-container i18n>Documents</ng-container>
</button>
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: permissionType }" [disabled]="!userCanEdit(object)">
<i-bs width="1em" height="1em" name="pencil"></i-bs>&nbsp;<ng-container i18n>Edit</ng-container>
</button>
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" [disabled]="!userCanDelete(object)">
<i-bs width="1em" height="1em" name="trash"></i-bs>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
</div>
</td>
</tr>
}
</tbody>
</table>
<ngb-pagination class="col-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
</div>
<div class="card border mb-3">
<table class="table table-striped align-middle shadow-sm mb-0">
<thead>
<tr>
<th scope="col">
<div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="all-objects" [disabled]="data.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
<label class="form-check-label" for="all-objects"></label>
</div>
@if (!isLoading) {
<div class="d-flex mb-2">
@if (collectionSize > 0) {
<div>
<ng-container i18n>{collectionSize, plural, =1 {One {{typeName}}} other {{{collectionSize || 0}} total {{typeNamePlural}}}}</ng-container>
@if (selectedObjects.size > 0) {
&nbsp;({{selectedObjects.size}} selected)
}
</div>
}
@if (collectionSize > 20) {
<ngb-pagination class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
}
</th>
<th scope="col" class="fw-normal" pngxSortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
<th scope="col" class="fw-normal d-none d-sm-table-cell" pngxSortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
<th scope="col" class="fw-normal" pngxSortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
@for (column of extraColumns; track column) {
<th scope="col" class="fw-normal" pngxSortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
}
<th scope="col" class="fw-normal" i18n>Actions</th>
</tr>
</thead>
<tbody>
@if (isLoading) {
<tr>
<td colspan="5">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</td>
</tr>
}
@for (object of data; track object) {
<tr (click)="toggleSelected(object); $event.stopPropagation();">
<td>
<div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="{{typeName}}{{object.id}}" [checked]="selectedObjects.has(object.id)" (click)="toggleSelected(object); $event.stopPropagation();">
<label class="form-check-label" for="{{typeName}}{{object.id}}"></label>
</div>
</td>
<td scope="row"><button class="btn btn-link ms-0 ps-0 text-start" (click)="userCanEdit(object) ? openEditDialog(object) : null">{{ object.name }}</button> </td>
<td scope="row" class="d-none d-sm-table-cell">{{ getMatching(object) }}</td>
<td scope="row">{{ object.document_count }}</td>
@for (column of extraColumns; track column) {
<td scope="row">
@if (column.rendersHtml) {
<div [innerHtml]="column.valueFn.call(null, object) | safeHtml"></div>
} @else {
{{ column.valueFn.call(null, object) }}
}
</td>
}
<td scope="row">
<div class="btn-group d-block d-sm-none">
<div ngbDropdown class="d-inline-block">
<button type="button" class="btn btn-link" id="actionsMenuMobile" (click)="$event.stopPropagation()" ngbDropdownToggle>
<i-bs name="three-dots-vertical"></i-bs>
</button>
<div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
<button (click)="filterDocuments(object)" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" ngbDropdownItem i18n>Filter Documents</button>
<button (click)="openEditDialog(object)" *pngxIfPermissions="{ action: PermissionAction.Change, type: permissionType }" ngbDropdownItem i18n>Edit</button>
<button class="text-danger" (click)="openDeleteDialog(object)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" ngbDropdownItem i18n>Delete</button>
</div>
</div>
</div>
<div class="btn-group d-none d-sm-block">
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<i-bs width="1em" height="1em" name="filter"></i-bs>&nbsp;<ng-container i18n>Documents</ng-container>
</button>
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: permissionType }" [disabled]="!userCanEdit(object)">
<i-bs width="1em" height="1em" name="pencil"></i-bs>&nbsp;<ng-container i18n>Edit</ng-container>
</button>
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Delete, type: permissionType }" [disabled]="!userCanDelete(object)">
<i-bs width="1em" height="1em" name="trash"></i-bs>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
@if (!isLoading) {
<div class="d-flex mb-2">
@if (collectionSize > 0) {
<div>
<ng-container i18n>{collectionSize, plural, =1 {One {{typeName}}} other {{{collectionSize || 0}} total {{typeNamePlural}}}}</ng-container>
@if (selectedObjects.size > 0) {
&nbsp;({{selectedObjects.size}} selected)
}
</div>
}
@if (collectionSize > 20) {
<ngb-pagination class="ms-auto" [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" [maxSize]="5" (pageChange)="reloadData()" size="sm" aria-label="Pagination"></ngb-pagination>
}
</div>
}
@@ -5,7 +5,7 @@
i18n-info
infoLink="usage/#workflows"
>
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editWorkflow()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Workflow }">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="editWorkflow()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Workflow }">
<i-bs name="plus-circle"></i-bs>&nbsp;<ng-container i18n>Add Workflow</ng-container>
</button>
</pngx-page-header>
+41
View File
@@ -0,0 +1,41 @@
export enum InstallType {
Containerized = 'containerized',
BareMetal = 'bare-metal',
}
export enum SystemStatusItemStatus {
OK = 'OK',
ERROR = 'ERROR',
}
export interface SystemStatus {
pngx_version: string
server_os: string
install_type: InstallType
storage: {
total: number
available: number
}
database: {
type: string
url: string
status: SystemStatusItemStatus
error?: string
migration_status: {
latest_migration: string
unapplied_migrations: string[]
}
}
tasks: {
redis_url: string
redis_status: SystemStatusItemStatus
redis_error: string
celery_status: SystemStatusItemStatus
index_status: SystemStatusItemStatus
index_last_modified: string // ISO date string
index_error: string
classifier_status: SystemStatusItemStatus
classifier_last_trained: string // ISO date string
classifier_error: string
}
}
+35
View File
@@ -2,6 +2,7 @@ import { ObjectWithId } from './object-with-id'
export enum WorkflowActionType {
Assignment = 1,
Removal = 2,
}
export interface WorkflowAction extends ObjectWithId {
type: WorkflowActionType
@@ -27,4 +28,38 @@ export interface WorkflowAction extends ObjectWithId {
assign_change_groups?: number[] // [Group.id]
assign_custom_fields?: number[] // [CustomField.id]
remove_tags?: number[] // Tag.id
remove_all_tags?: boolean
remove_document_types?: number[] // [DocumentType.id]
remove_all_document_types?: boolean
remove_correspondents?: number[] // [Correspondent.id]
remove_all_correspondents?: boolean
remove_storage_paths?: number[] // [StoragePath.id]
remove_all_storage_paths?: boolean
remove_owners?: number[] // [User.id]
remove_all_owners?: boolean
remove_view_users?: number[] // [User.id]
remove_view_groups?: number[] // [Group.id]
remove_change_users?: number[] // [User.id]
remove_change_groups?: number[] // [Group.id]
remove_all_permissions?: boolean
remove_custom_fields?: number[] // [CustomField.id]
remove_all_custom_fields?: boolean
}
@@ -183,7 +183,7 @@ export class DocumentService extends AbstractPaperlessService<Document> {
update(o: Document): Observable<Document> {
// we want to only set created_date
o.created = undefined
o.remove_inbox_tags = this.settingsService.get(
o.remove_inbox_tags = !!this.settingsService.get(
SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS
)
return super.update(o)
@@ -0,0 +1,35 @@
import { TestBed } from '@angular/core/testing'
import { SystemStatusService } from './system-status.service'
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing'
import { environment } from 'src/environments/environment'
describe('SystemStatusService', () => {
let httpTestingController: HttpTestingController
let service: SystemStatusService
beforeEach(() => {
TestBed.configureTestingModule({
providers: [SystemStatusService],
imports: [HttpClientTestingModule],
})
httpTestingController = TestBed.inject(HttpTestingController)
service = TestBed.inject(SystemStatusService)
})
afterEach(() => {
httpTestingController.verify()
})
it('calls get status endpoint', () => {
service.get().subscribe()
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}status/`
)
expect(req.request.method).toEqual('GET')
})
})
@@ -0,0 +1,20 @@
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { Observable } from 'rxjs'
import { SystemStatus } from '../data/system-status'
import { environment } from 'src/environments/environment'
@Injectable({
providedIn: 'root',
})
export class SystemStatusService {
private endpoint = 'status'
constructor(private http: HttpClient) {}
get(): Observable<SystemStatus> {
return this.http.get<SystemStatus>(
`${environment.apiBaseUrl}${this.endpoint}/`
)
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ export const environment = {
apiBaseUrl: document.baseURI + 'api/',
apiVersion: '5',
appTitle: 'Paperless-ngx',
version: '2.5.4',
version: '2.6.0',
webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
webSocketBaseUrl: base_url.pathname + 'ws/',
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Loaded 100 of 190 files, more files were not shown because too many files have changed in this diff. Show more