Compare commits

..
Author SHA1 Message Date
jonaswinkler 5e79429dfb models and tests 2021-01-28 16:31:31 +01:00
154 changed files with 2388 additions and 9313 deletions

No files matched your search

-6
View File
@@ -11,12 +11,6 @@ assignees: ''
=> Before opening an issue, please check the documentation and see if it helps you resolve your issue: https://paperless-ng.readthedocs.io/en/latest/troubleshooting.html
=> Please also make sure that you followed the installation instructions.
=> Please search the issues and look for similar issues before opening a bug report.
=> If you would like to submit a feature request please submit one under https://github.com/jonaswinkler/paperless-ng/discussions/categories/feature-requests
=> If you encounter issues while installing of configuring Paperless-ng, please post that in the "Support" section of the discussions. Remember that Paperless successfully runs on a variety of different systems. If paperless does not start, it's probably an issue with your system, and not an issue of paperless.
=> Don't remove the [BUG] prefix from the title.
-->
**Describe the bug**
+18
View File
@@ -0,0 +1,18 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature Request] Consice and clear description of your feature request"
labels: ''
assignees: ''
---
<!--
=> We already have lots of feature requests open. Please search the existing requests first and look for feature requests similar to yours.
-->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
+2 -8
View File
@@ -1,7 +1,7 @@
---
name: Other
about: Anything that is not a feature request or bug.
title: "[Other] Title of your issue"
title: ''
labels: ''
assignees: ''
@@ -9,12 +9,6 @@ assignees: ''
<!--
=> Discussions, Feedback and other suggestions belong in the "Discussion" section and not on the issue tracker.
=> If you would like to submit a feature request please submit one under https://github.com/jonaswinkler/paperless-ng/discussions/categories/feature-requests
=> If you encounter issues while installing of configuring Paperless-ng, please post that in the "Support" section of the discussions. Remember that Paperless successfully runs on a variety of different systems. If paperless does not start, it's probably is an issue with your system, and not an issue of paperless.
=> Don't remove the [Other] prefix from the title.
=> Discussions, Feedback and other suggestions belong in the "Disussion" section and not on the issue tracker.
-->
+11 -24
View File
@@ -1,32 +1,19 @@
---
name: Ansible Role
on:
push:
branches-ignore:
- 'translations**'
pull_request:
branches-ignore:
- 'translations**'
on: [push, pull_request]
jobs:
# https://molecule.readthedocs.io/en/latest/ci.html#github-actions
test:
runs-on: ubuntu-latest
# https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#github-context
if: github.event_name == 'pull_request' || (github.event_name == 'push' && contains(github.ref, 'refs/heads/'))
steps:
- name: Check out the codebase
uses: actions/checkout@v2
if: github.event_name != 'pull_request'
with:
path: "${{ github.repository }}"
- name: Check out the codebase
uses: actions/checkout@v2
if: github.event_name == 'pull_request'
with:
# merge commit is not available from tree at this point in time
# https://github.com/actions/checkout#checkout-pull-request-head-commit-instead-of-merge-commit
ref: "${{ github.event.pull_request.head.sha }}"
path: "${{ github.repository }}"
- name: Set up Python
uses: actions/setup-python@v2
- name: Set up Docker
@@ -34,20 +21,20 @@ jobs:
- name: Install dependencies
run: |
python3 -m pip install --upgrade pip
python3 -m pip install molecule[ansible,docker] jmespath
python3 -m pip install molecule[ansible,docker]
ansible --version
docker --version
molecule --version
python --version
- name: Test installation/build/upgrade with molecule
- name: Test fresh installation with molecule
run: |
cd ansible
molecule create
molecule verify
molecule converge
molecule idempotence
molecule verify
molecule destroy
molecule test -s fresh
working-directory: "${{ github.repository }}"
- name: Test release update with molecule
run: |
cd ansible
molecule test -s update
working-directory: "${{ github.repository }}"
# # https://galaxy.ansible.com/docs/contributing/importing.html
# release:
+3 -11
View File
@@ -1,13 +1,6 @@
name: ci
on:
push:
tags: ng-*
branches-ignore:
- 'translations**'
pull_request:
branches-ignore:
- 'translations**'
on: [push, pull_request]
jobs:
documentation:
@@ -55,7 +48,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9']
python-version: ['3.6', '3.7', '3.8']
fail-fast: false
steps:
-
@@ -162,7 +155,6 @@ jobs:
mkdir dist/paperless-ng/scripts
cp .dockerignore .env Dockerfile Pipfile Pipfile.lock LICENSE README.md requirements.txt dist/paperless-ng/
cp paperless.conf.example dist/paperless-ng/paperless.conf
cp gunicorn.conf.py dist/paperless-ng/gunicorn.conf.py
cp docker/ dist/paperless-ng/docker -r
cp scripts/*.service scripts/*.sh dist/paperless-ng/scripts/
cp src/ dist/paperless-ng/src -r
@@ -233,7 +225,7 @@ jobs:
# build and push image to docker hub.
build-docker-image:
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/ng-'))
if: github.event_name == 'push' && (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/ng-'))
runs-on: ubuntu-latest
needs: [frontend, tests]
steps:
+29 -29
View File
@@ -10,6 +10,10 @@ RUN ./configure && make
FROM python:3.7-slim
WORKDIR /usr/src/paperless/
COPY requirements.txt ./
# Binary dependencies
RUN apt-get update \
&& apt-get -y --no-install-recommends install \
@@ -45,24 +49,16 @@ RUN apt-get update \
tesseract-ocr-spa \
unpaper \
zlib1g \
&& rm -rf /var/lib/apt/lists/*
# This pulls in updated dependencies from bullseye to fix some issues with file type detection.
# TODO: Remove this once bullseye releases.
&& echo "deb http://deb.debian.org/debian bullseye main" > /etc/apt/sources.list.d/bullseye.list \
RUN echo "deb http://deb.debian.org/debian bullseye main" > /etc/apt/sources.list.d/bullseye.list \
&& apt-get update \
&& apt-get install --no-install-recommends -y file libmagic-dev \
&& rm -rf /var/lib/apt/lists/* \
&& rm /etc/apt/sources.list.d/bullseye.list
# copy jbig2enc
COPY --from=jbig2enc /usr/src/jbig2enc/src/.libs/libjbig2enc* /usr/local/lib/
COPY --from=jbig2enc /usr/src/jbig2enc/src/jbig2 /usr/local/bin/
COPY --from=jbig2enc /usr/src/jbig2enc/src/*.h /usr/local/include/
WORKDIR /usr/src/paperless/src/
COPY requirements.txt ../
# Python dependencies
RUN apt-get update \
&& apt-get -y --no-install-recommends install \
@@ -71,36 +67,40 @@ RUN apt-get update \
libpq-dev \
libqpdf-dev \
&& python3 -m pip install --upgrade --no-cache-dir supervisor \
&& python3 -m pip install --no-cache-dir -r ../requirements.txt \
&& python3 -m pip install --no-cache-dir -r requirements.txt \
&& apt-get -y purge build-essential libqpdf-dev \
&& apt-get -y autoremove --purge \
&& rm -rf /var/lib/apt/lists/*
&& rm -rf /var/lib/apt/lists/* \
&& mkdir /var/log/supervisord /var/run/supervisord
# setup docker-specific things
COPY docker/ ./docker/
RUN cd docker \
&& cp imagemagick-policy.xml /etc/ImageMagick-6/policy.xml \
&& mkdir /var/log/supervisord /var/run/supervisord \
&& cp supervisord.conf /etc/supervisord.conf \
&& cp docker-entrypoint.sh /sbin/docker-entrypoint.sh \
&& chmod 755 /sbin/docker-entrypoint.sh \
&& chmod +x install_management_commands.sh \
&& ./install_management_commands.sh \
&& cd .. \
&& rm docker -rf
# copy scripts
# this fixes issues with imagemagick and PDF
COPY docker/imagemagick-policy.xml /etc/ImageMagick-6/policy.xml
COPY docker/gunicorn.conf.py ./
COPY docker/supervisord.conf /etc/supervisord.conf
COPY docker/docker-entrypoint.sh /sbin/docker-entrypoint.sh
# copy jbig2enc
COPY --from=jbig2enc /usr/src/jbig2enc/src/.libs/libjbig2enc* /usr/local/lib/
COPY --from=jbig2enc /usr/src/jbig2enc/src/jbig2 /usr/local/bin/
COPY --from=jbig2enc /usr/src/jbig2enc/src/*.h /usr/local/include/
COPY gunicorn.conf.py ../
# copy app
COPY src/ ./
COPY src/ ./src/
# add users, setup scripts
RUN addgroup --gid 1000 paperless \
&& useradd --uid 1000 --gid paperless --home-dir /usr/src/paperless paperless \
&& chown -R paperless:paperless ../ \
&& sudo -HEu paperless python3 manage.py collectstatic --clear --no-input \
&& sudo -HEu paperless python3 manage.py compilemessages
&& chown -R paperless:paperless . \
&& chmod 755 /sbin/docker-entrypoint.sh
WORKDIR /usr/src/paperless/src/
RUN sudo -HEu paperless python3 manage.py collectstatic --clear --no-input
RUN sudo -HEu paperless python3 manage.py compilemessages
VOLUME ["/usr/src/paperless/data", "/usr/src/paperless/media", "/usr/src/paperless/consume", "/usr/src/paperless/export"]
ENTRYPOINT ["/sbin/docker-entrypoint.sh"]
+5 -20
View File
@@ -17,45 +17,30 @@ django-filter = "~=2.4.0"
django-q = "~=1.3.4"
djangorestframework = "~=3.12.2"
filelock = "*"
fuzzywuzzy = {extras = ["speedup"], version = "*"}
fuzzywuzzy = "*"
gunicorn = "*"
imap-tools = "*"
langdetect = "*"
# numpy 1.20.0 drops python 3.6 support
numpy = "~=1.19.5"
pdftotext = "*"
pathvalidate = "*"
# pinned to 8.1.0, since aarch64 wheels might not be available beyond that https://github.com/python-pillow/Pillow/issues/5202
pillow = "==8.1.0"
pikepdf = "~=2.5.0"
pikepdf = "~=2.2.5"
python-gnupg = "*"
python-dotenv = "*"
python-dateutil = "*"
python-Levenshtein = "*"
python-magic = "*"
psycopg2-binary = "*"
redis = "*"
# Pinned because aarch64 wheels and updates cause warnings when loading the classifier model.
scikit-learn="==0.24.0"
# Prevent scipy updates because 1.6 is incompatible with python 3.6
scipy="~=1.5.4"
scikit-learn="~=0.24.0"
whitenoise = "~=5.2.0"
watchdog = "*"
whoosh="~=2.7.4"
inotifyrecursive = "~=0.3.4"
ocrmypdf = "~=11.6"
ocrmypdf = "~=11.4.5"
tqdm = "*"
tika = "*"
# TODO: This will sadly also install daphne+dependencies,
# which an ASGI server we don't need. Adds about 15MB image size.
channels = "~=3.0"
channels-redis = "*"
uvicorn = {extras = ["standard"], version = "*"}
concurrent-log-handler = "*"
django-redis = "*"
# uvloop 0.15+ incompatible with python 3.6
uvloop = "~=0.14.0"
# TODO: keep an eye on piwheel builds and update this once available (https://www.piwheels.org/project/cryptography/)
cryptography = "~=3.3.2"
[dev-packages]
coveralls = "*"
Generated
+215 -664
View File
File diff suppressed because it is too large. Load diff
+8 -9
View File
@@ -1,5 +1,4 @@
[![ci](https://github.com/jonaswinkler/paperless-ng/workflows/ci/badge.svg)](https://github.com/jonaswinkler/paperless-ng/actions)
![Ansible Role](https://github.com/jonaswinkler/paperless-ng/workflows/Ansible%20Role/badge.svg)
[![Documentation Status](https://readthedocs.org/projects/paperless-ng/badge/?version=latest)](https://paperless-ng.readthedocs.io/en/latest/?badge=latest)
[![Gitter](https://badges.gitter.im/paperless-ng/community.svg)](https://gitter.im/paperless-ng/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Docker Hub Pulls](https://img.shields.io/docker/pulls/jonaswinkler/paperless-ng.svg)](https://hub.docker.com/r/jonaswinkler/paperless-ng)
@@ -9,7 +8,11 @@
[Paperless](https://github.com/the-paperless-project/paperless) is an application by Daniel Quinn and contributors that indexes your scanned documents and allows you to easily search for documents and store metadata alongside your documents.
Paperless-ng is a fork of the original project, adding a new interface and many other changes under the hood. For a detailed list of changes, have a look at the [change log](https://paperless-ng.readthedocs.io/en/latest/changelog.html) in the documentation.
Paperless-ng is a fork of the original project, adding a new interface and many other changes under the hood. For a detailed list of changes, have a look at the changelog in the documentation.
# Survey
If you already used Paperless-ng for a bit, would like to give some anonymous feedback, and help me decide on what to focus on next: I've created a survey, [see here](https://github.com/jonaswinkler/paperless-ng/issues/402). Thank you!
# How it Works
@@ -75,17 +78,13 @@ Paperless is currently available in English, German, Dutch and French. Translati
If you want to see paperless in your own language, request that language at transifex and you can start translating after I approve the language.
# Feature Requests
# Suggestions? Questions? Something not working?
Feature requests can be submitted via [GitHub Discussions](https://github.com/jonaswinkler/paperless-ng/discussions/categories/feature-requests), you can search for existing ideas, add your own and vote for the ones you care about! Note that some older feature requests can also be found under [issues](https://github.com/jonaswinkler/paperless-ng/issues).
# Questions? Something not working?
For bugs please [open an issue](https://github.com/jonaswinkler/paperless-ng/issues) or [start a discussion](https://github.com/jonaswinkler/paperless-ng/discussions) if you have questions.
Please open an issue and start a discussion about it!
## Feel like helping out?
There's still lots of things to be done, just have a look at open issues & discussions. If you feel like contributing to the project, please do! Bug fixes and improvements to the front end (I just can't seem to get some of these CSS things right) are always welcome. The documentation has some basic information on how to get started.
There's still lots of things to be done, just have a look at that issue log. If you feel like contributing to the project, please do! Bug fixes and improvements to the front end (I just can't seem to get some of these CSS things right) are always welcome. The documentation has some basic information on how to get started.
If you want to implement something big: Please start a discussion about that in the issues! Maybe I've already had something similar in mind and we can make it happen together. However, keep in mind that the general roadmap is to make the existing features stable and get them tested. See the roadmap above.
+1 -1
View File
@@ -107,7 +107,7 @@ Example Playbook
paperlessng_db_type: postgresql
paperlessng_db_pass: PLEASEPROVIDEASTRONGPASSWORDHERE
paperlessng_secret_key: AGAINPLEASECHANGETHISNOW
paperless_secret_key: AGAINPLEASECHANGETHISNOW
paperlessng_ocr_languages:
- eng
+9 -9
View File
@@ -1,5 +1,5 @@
---
paperlessng_version: latest # 'latest', release number, or github branch/tag/commit/ref
paperlessng_version: 0.9.14
# Required services
paperlessng_redis_host: localhost
@@ -23,14 +23,14 @@ paperlessng_filename_format:
paperlessng_virtualenv: "{{ paperlessng_directory }}/.venv"
# Hosting & Security
paperlessng_secret_key: PLEASECHANGETHISFORTHELOVEOFGOD
paperlessng_allowed_hosts: "*"
paperlessng_cors_allowed_hosts: http://localhost:8000
paperlessng_force_script_name:
paperlessng_static_url: /static/
paperlessng_auto_login_username:
paperlessng_cookie_prefix: ""
paperlessng_enable_http_remote_user: False
paperless_secret_key: PLEASECHANGETHISFORTHELOVEOFGOD
paperless_allowed_hosts: "*"
paperless_cors_allowed_hosts: http://localhost:8000
paperless_force_script_name:
paperless_static_url: /static/
paperless_auto_login_username:
paperless_cookie_prefix: ""
paperless_enable_http_remote_user: False
# OCR settings
paperlessng_ocr_languages:
-94
View File
@@ -1,94 +0,0 @@
---
- name: Verify
hosts: all
gather_facts: false
vars_files:
- ../../defaults/main.yml
tasks:
- name: check if webserver is up
uri:
url: "http://{{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}"
status_code: [200, 302]
return_content: yes
register: landingpage
failed_when: "'Sign in</button>' not in landingpage.content"
- name: generate random name and content
set_fact:
content: "{{ lookup('password', '/dev/null length=65536 chars=ascii_letters') }}"
filename: "{{ lookup('password', '/dev/null length=8 chars=ascii_letters') }}"
- name: check if document posting works
uri:
url: "http://{{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}/api/documents/post_document/"
method: POST
body_format: form-multipart
body:
document:
content: "{{ content }}"
filename: "{{ filename }}.txt"
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
Content-Type: text/plain
return_content: yes
register: post_document
failed_when: "'OK' not in post_document.content"
- name: verify uploaded document has been accepted
uri:
url: "http://{{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}/api/logs/paperless/"
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: logs
failed_when: "('Consuming ' + filename + '.txt') not in logs.content"
- name: sleep till consumption finished
pause:
seconds: 10
- name: verify uploaded document has been consumed
uri:
url: "http://{{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}/api/logs/paperless/"
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: logs
failed_when: "filename + ' consumption finished' not in logs.content"
- name: get documents
uri:
url: "http://{{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}/api/documents/"
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: documents
- name: set document index
set_fact:
index: "{{ documents.json['results'][0]['id'] }}"
- name: verify uploaded document is avaiable
uri:
url: "http://{{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}/api/documents/{{ index }}/"
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: document
failed_when: "'Not found.' in document.content or content not in document.json['content']"
- name: check if deleting uploaded document works
uri:
url: "http://{{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}/api/documents/bulk_edit/"
method: POST
body_format: json
body:
documents: ["{{ index }}"]
method: delete
parameters: {}
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
register: delete_document
failed_when: "'OK' not in delete_document.json['result']"
+7
View File
@@ -0,0 +1,7 @@
---
- name: fresh installation
hosts: all
tasks:
- name: install paperless-ng with default parameters
include_role:
name: ansible
File renamed without changes.
+60
View File
@@ -0,0 +1,60 @@
---
- name: Verify
hosts: all
gather_facts: false
vars_files:
- ../../defaults/main.yml
tasks:
- name: check if webserver is up
uri:
url: http://localhost:8000
status_code: [200, 302]
return_content: yes
register: landingpage
failed_when: "'Sign in</button>' not in landingpage.content"
- name: check if document posting works
uri:
url: http://localhost:8000/api/documents/post_document/
method: POST
body_format: form-multipart
body:
document:
content: FOO
filename: document.txt
mime_type: text/plain
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: post_document
failed_when: "'OK' not in post_document.content"
- name: verify uploaded document has been accepted
uri:
url: http://localhost:8000/api/logs/
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: logs
failed_when: "'Consuming document.txt' not in logs.content"
# assumes txt consumption finished by now, might have to sleep a bit
- name: verify uploaded document has been consumed
uri:
url: http://localhost:8000/api/logs/
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: logs
failed_when: "'document consumption finished' not in logs.content"
- name: verify uploaded document is avaiable
uri:
url: http://localhost:8000/api/documents/1/
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: document
failed_when: "'Not found.' in document.content or 'FOO' not in document.content"
@@ -2,9 +2,10 @@
- name: update previous release to newest release
hosts: all
tasks:
- name: set current github commit as version when available
- name: set current version as installation target
set_fact:
paperlessng_version: "{{ lookup('env', 'GITHUB_SHA') | default('master', True) }}"
paperlessng_version: 0.9.14
- name: update to newest paperless-ng release
include_role:
name: ansible
+35
View File
@@ -0,0 +1,35 @@
---
dependency:
name: galaxy
driver:
name: docker
platforms:
- name: ubuntu_focal
image: jrei/systemd-ubuntu:20.04
privileged: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
tmpfs:
- /tmp
- /run
- /run/lock
override_command: False
# ubuntu 18.04 bionic works except that
# the default redis configuration expects IPv6 which is not enabled in docker by default
# the default Python environment is configured for ASCII instead of UTF-8
# ubuntu 16.04 xenial only has Python 3.5 which is EOL and breaks multiple dependencies
- name: debian_buster
image: jrei/systemd-debian:10
privileged: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
tmpfs:
- /tmp
- /run
- /run/lock
override_command: False
# debian 9 stretch only has Python 3.5 which is EOL and breaks multiple dependencies
provisioner:
name: ansible
verifier:
name: ansible
@@ -3,7 +3,7 @@
tasks:
- name: set previous version as installation target
set_fact:
paperlessng_version: latest
paperlessng_version: 0.9.13
- name: install previous paperless-ng release
include_role:
+60
View File
@@ -0,0 +1,60 @@
---
- name: Verify
hosts: all
gather_facts: false
vars_files:
- ../../defaults/main.yml
tasks:
- name: check if webserver is up
uri:
url: http://localhost:8000
status_code: [200, 302]
return_content: yes
register: landingpage
failed_when: "'Sign in</button>' not in landingpage.content"
- name: check if document posting works
uri:
url: http://localhost:8000/api/documents/post_document/
method: POST
body_format: form-multipart
body:
document:
content: FOO
filename: document.txt
mime_type: text/plain
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: post_document
failed_when: "'OK' not in post_document.content"
- name: verify uploaded document has been accepted
uri:
url: http://localhost:8000/api/logs/
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: logs
failed_when: "'Consuming document.txt' not in logs.content"
# assumes txt consumption finished by now, might have to sleep a bit
- name: verify uploaded document has been consumed
uri:
url: http://localhost:8000/api/logs/
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: logs
failed_when: "'document consumption finished' not in logs.content"
- name: verify uploaded document is avaiable
uri:
url: http://localhost:8000/api/documents/1/
headers:
Authorization: 'Basic {{ (paperlessng_superuser_name + ":" + paperlessng_superuser_password) | b64encode }}'
return_content: yes
register: document
failed_when: "'Not found.' in document.content or 'FOO' not in document.content"
-6
View File
@@ -1,6 +0,0 @@
---
- name: extract paperless-ng
unarchive:
src: "https://github.com/jonaswinkler/paperless-ng/releases/download/ng-{{ paperlessng_version }}/paperless-ng-{{ paperlessng_version }}.tar.xz"
remote_src: yes
dest: "{{ tempdir.path }}"
-112
View File
@@ -1,112 +0,0 @@
---
- name: install dev dependencies
apt:
pkg:
- git
- npm
- gettext
- name: create output directories
file:
path: "{{ item }}"
state: directory
owner: "{{ paperlessng_system_user }}"
group: "{{ paperlessng_system_group }}"
mode: "750"
with_items:
- "{{ tempdir.path }}/paperless-ng"
- "{{ tempdir.path }}/paperless-ng/scripts"
- block:
- name: create temporary git directory
tempfile:
state: directory
path: "{{ paperlessng_directory }}"
register: gitdir
- name: pull paperless-ng
git:
repo: https://github.com/jonaswinkler/paperless-ng.git
dest: "{{ gitdir.path }}"
version: "{{ paperlessng_version }}"
refspec: "+refs/pull/*:refs/pull/*"
- name: compile frontend
command:
cmd: "{{ item }}"
args:
chdir: "{{ gitdir.path }}/src-ui"
failed_when: false
with_items:
- npm install -g @angular/cli
- npm install
- ./node_modules/.bin/ng build --prod
- name: copy application into place
copy:
src: "{{ gitdir.path }}/{{ item.src }}"
remote_src: yes
dest: "{{ tempdir.path }}/paperless-ng/{{ item.dest | default('') }}"
with_items:
- src: CONTRIBUTING.md
- src: LICENSE
- src: Pipfile
- src: Pipfile.lock
- src: README.md
- src: requirements.txt
- src: gunicorn.conf.py
- src: paperless.conf.example
dest: "paperless.conf"
- name: glob all scripts
find:
paths: ["{{ gitdir.path }}/scripts/"]
patterns:
- "*.service"
- "*.sh"
register: glob
- name: copy scripts
copy:
src: "{{ item.path }}"
remote_src: yes
dest: "{{ tempdir.path }}/paperless-ng/scripts/"
with_items:
- "{{ glob.files }}"
- name: copy sources
command:
cmd: "cp -r src/ {{ tempdir.path }}/paperless-ng/src"
args:
chdir: "{{ gitdir.path }}"
- name: create paperlessng venv
command:
cmd: "python3 -m virtualenv {{ gitdir.path }}/.venv/ -p /usr/bin/python3"
- name: install paperlessng requirements
command:
cmd: "{{ gitdir.path }}/.venv/bin/python3 -m pip install -r {{ gitdir.path }}/requirements.txt"
- name: compile messages
command: "{{ gitdir.path }}/.venv/bin/python3 manage.py compilemessages"
args:
chdir: "{{ tempdir.path }}/paperless-ng/src/"
- name: collect static files
command: "{{ gitdir.path }}/.venv/bin/python3 manage.py collectstatic --no-input"
args:
chdir: "{{ tempdir.path }}/paperless-ng/src/"
- name: remove pycache directories
shell: find . -name __pycache__ | xargs rm -r
args:
chdir: "{{ tempdir.path }}"
- name: remove temporary git directory
file:
path: "{{ gitdir.path }}"
state: absent
become: yes
become_user: "{{ paperlessng_system_user }}"
+83 -169
View File
@@ -34,13 +34,7 @@
- build-essential
- python3-setuptools
- python3-wheel
# upstream virtualenv in Ubuntu 20.04 is broken
# https://github.com/pypa/virtualenv/issues/1873
- name: install python virtualenv
pip:
name: virtualenv
extra_args: --upgrade
- python3-virtualenv
- name: install ocr languages
apt:
@@ -103,147 +97,71 @@
# GNUPG_HOME required due to paperless db.py
create_home: yes
- block:
- name: get latest release version
uri:
url: https://api.github.com/repos/jonaswinkler/paperless-ng/releases/latest
method: GET
register: latest_release
- name: parse latest release version
set_fact:
paperlessng_version: "{{ latest_release.json['tag_name'] }}"
when: paperlessng_version == "latest"
- name: check if version is ref
fail:
msg: "Specifying `paperlessng_version` as git ref may not work as expected!"
ignore_errors: True # Output failure (as warning), but don't consider play failed
when: paperlessng_version.startswith('refs/')
- block:
- name: sanitize version string
set_fact:
paperlessng_version: "{{ paperlessng_version | regex_replace('^ng-(\\d+\\.\\d+\\.\\d+)$', '\\1') }}"
- name: get tag data
uri:
url: https://api.github.com/repos/jonaswinkler/paperless-ng/tags
method: GET
register: tags
- name: get commit for target tag
set_fact:
paperlessng_commit: "{{ tags.json | json_query('[?name==`ng-' + paperlessng_version +'`] | [0].commit.sha') }}"
when: paperlessng_version | regex_search("^(ng-)?(\d+\.\d+\.\d+)$")
- block:
- name: check if version is branch
uri:
url: "https://api.github.com/repos/jonaswinkler/paperless-ng/branches/{{ paperlessng_version }}"
method: GET
status_code: [200, 404]
register: branch
- name: get commit for target branch
set_fact:
paperlessng_commit: "{{ branch.json | json_query('commit.sha') }}"
when: branch.status == 200
- block:
- name: check if version is commit-or-ref
uri:
url: "https://api.github.com/repos/jonaswinkler/paperless-ng/commits/{{ paperlessng_version }}"
method: GET
status_code: [200, 404, 422]
register: commit
- name: get commit for target commit-or-ref
set_fact:
paperlessng_commit: "{{ commit.json | json_query('sha') }}"
when: commit.status == 200
- name: fail
fail:
msg: "Can not determine commit from `paperlessng_version=={{ paperlessng_version }}`!"
when: commit.status != 200
when: branch.status == 404
when: not(paperlessng_version | regex_search("^(ng-)?(\d+\.\d+\.\d+)$"))
- name: check for paperless-ng installation
command:
cmd: "cat {{ paperlessng_directory }}/.installed_version"
changed_when: '"No such file or directory" in paperlessng_current_commit.stderr or paperlessng_current_commit.stdout != paperlessng_commit | string'
cmd: 'grep -Po "(?<=Paperless-ng )\d+\.\d+\.\d+" {{ paperlessng_directory }}/docs/changelog.html'
changed_when: '"No such file or directory" in paperlessng_current_version.stderr or paperlessng_current_version.stdout != paperlessng_version | string'
failed_when: false
ignore_errors: yes
register: paperlessng_current_commit
register: paperlessng_current_version
- name: register current state
set_fact:
fresh_installation: '{{ "No such file or directory" in paperlessng_current_commit.stderr }}'
update_installation: '{{ "No such file or directory" not in paperlessng_current_commit.stderr and paperlessng_current_commit.stdout != paperlessng_commit | string }}'
reconfigure_only: "{{ paperlessng_current_commit.stdout == paperlessng_commit | string }}"
fresh_installation: '{{ "No such file or directory" in paperlessng_current_version.stderr }}'
update_installation: '{{ "No such file or directory" not in paperlessng_current_version.stderr and paperlessng_current_version.stdout != paperlessng_version | string }}'
reconfigure_only: '{{ paperlessng_current_version.stdout == paperlessng_version | string }}'
- block:
- name: backup current paperless-ng installation
copy:
src: "{{ paperlessng_directory }}"
remote_src: yes
dest: "{{ paperlessng_directory }}-{{ ansible_date_time.iso8601 }}/"
- name: remove current paperless sources
file:
path: "{{ paperlessng_directory }}/{{ item }}"
state: absent
with_items:
- docker
- docs
- scripts
- src
- static
- name: backup current paperless-ng installation
copy:
src: "{{ paperlessng_directory }}"
remote_src: yes
dest: "{{ paperlessng_directory }}-{{ ansible_date_time.iso8601 }}/"
when: update_installation
- block:
- name: create paperless-ng directory and set permissions
file:
path: "{{ paperlessng_directory }}"
state: directory
owner: "{{ paperlessng_system_user }}"
group: "{{ paperlessng_system_group }}"
mode: "750"
- name: create temporary directory
become: yes
become_user: "{{ paperlessng_system_user }}"
tempfile:
state: directory
path: "{{ paperlessng_directory }}"
register: tempdir
- name: check if version is available as release archive
uri:
url: "https://github.com/jonaswinkler/paperless-ng/releases/download/ng-{{ paperlessng_version }}/paperless-ng-{{ paperlessng_version }}.tar.xz"
method: GET
status_code: [200, 404]
register: release_archive
- name: install paperless-ng from source
include_tasks: install-source.yml
when: release_archive.status == 404
- name: install paperless-ng from release archive
include_tasks: install-release.yml
when: release_archive.status == 200
- name: change owner and permissions of paperless-ng
command:
cmd: "{{ item }}"
warn: false
with_items:
- "chown -R {{ paperlessng_system_user }}:{{ paperlessng_system_group }} {{ tempdir.path }}"
- "find {{ tempdir.path }} -type d -exec chmod 0750 {} ;"
- "find {{ tempdir.path }} -type f -exec chmod 0640 {} ;"
- name: move paperless-ng
command:
cmd: "cp -a {{ tempdir.path }}/paperless-ng/. {{ paperlessng_directory }}"
- name: store commit hash of installed version
copy:
content: "{{ paperlessng_commit }}"
dest: "{{ paperlessng_directory }}/.installed_version"
owner: "{{ paperlessng_system_user }}"
group: "{{ paperlessng_system_group }}"
mode: "0440"
- name: remove temporary directory
file:
path: "{{ tempdir.path }}"
state: absent
- name: remove current paperless sources
file:
path: "{{ paperlessng_directory }}/{{ item }}"
state: absent
with_items:
- docker
- docs
- scripts
- src
- static
when: update_installation
- name: create temporary directory
tempfile:
state: directory
register: tempdir
when: not reconfigure_only
- name: extract paperless-ng
unarchive:
src: "https://github.com/jonaswinkler/paperless-ng/releases/download/ng-{{ paperlessng_version }}/paperless-ng-{{ paperlessng_version }}.tar.xz"
remote_src: yes
dest: "{{ tempdir.path }}"
when: not reconfigure_only
- name: change owner and permissions of paperless-ng
command:
cmd: "{{ item }}"
warn: false
with_items:
- "chown -R {{ paperlessng_system_user }}:{{ paperlessng_system_group }} {{ tempdir.path }}"
- "find {{ tempdir.path }} -type d -exec chmod 0750 {} ;"
- "find {{ tempdir.path }} -type f -exec chmod 0640 {} ;"
when: not reconfigure_only
- name: move paperless-ng
command:
cmd: "cp -a {{ tempdir.path }}/paperless-ng/. {{ paperlessng_directory }}"
when: not reconfigure_only
- name: remove temporary directory
file:
path: "{{ tempdir.path }}"
state: absent
when: not reconfigure_only
- name: create paperless-ng directories and set permissions
@@ -254,6 +172,7 @@
group: "{{ paperlessng_system_group }}"
mode: "750"
with_items:
- "{{ paperlessng_directory }}"
- "{{ paperlessng_consumption_dir }}"
- "{{ paperlessng_data_dir }}"
- "{{ paperlessng_media_root }}"
@@ -261,7 +180,7 @@
- name: rename initial config
command:
cmd: "mv -f {{ paperlessng_directory }}/paperless.conf {{ paperlessng_directory }}/paperless.conf.template"
cmd: "mv {{ paperlessng_directory }}/paperless.conf {{ paperlessng_directory }}/paperless.conf.template"
removes: "{{ paperlessng_directory }}/paperless.conf"
- name: configure paperless-ng
@@ -286,21 +205,21 @@
line: "PAPERLESS_FILENAME_FORMAT={{ paperlessng_filename_format }}"
# Hosting & Security
- regexp: PAPERLESS_SECRET_KEY
line: "PAPERLESS_SECRET_KEY={{ paperlessng_secret_key }}"
line: "PAPERLESS_SECRET_KEY={{ paperless_secret_key }}"
- regexp: PAPERLESS_ALLOWED_HOSTS
line: "PAPERLESS_ALLOWED_HOSTS={{ paperlessng_allowed_hosts }}"
line: "PAPERLESS_ALLOWED_HOSTS={{ paperless_allowed_hosts }}"
- regexp: PAPERLESS_CORS_ALLOWED_HOSTS
line: "PAPERLESS_CORS_ALLOWED_HOSTS={{ paperlessng_cors_allowed_hosts }}"
line: "PAPERLESS_CORS_ALLOWED_HOSTS={{ paperless_cors_allowed_hosts }}"
- regexp: PAPERLESS_FORCE_SCRIPT_NAME
line: "PAPERLESS_FORCE_SCRIPT_NAME={{ paperlessng_force_script_name }}"
line: "PAPERLESS_FORCE_SCRIPT_NAME={{ paperless_force_script_name }}"
- regexp: PAPERLESS_STATIC_URL
line: "PAPERLESS_STATIC_URL={{ paperlessng_static_url }}"
line: "PAPERLESS_STATIC_URL={{ paperless_static_url }}"
- regexp: PAPERLESS_AUTO_LOGIN_USERNAME
line: "PAPERLESS_AUTO_LOGIN_USERNAME={{ paperlessng_auto_login_username }}"
line: "PAPERLESS_AUTO_LOGIN_USERNAME={{ paperless_auto_login_username }}"
- regexp: PAPERLESS_COOKIE_PREFIX
line: "PAPERLESS_COOKIE_PREFIX={{ paperlessng_cookie_prefix }}"
line: "PAPERLESS_COOKIE_PREFIX={{ paperless_cookie_prefix }}"
- regexp: PAPERLESS_ENABLE_HTTP_REMOTE_USER
line: "PAPERLESS_ENABLE_HTTP_REMOTE_USER={{ paperlessng_enable_http_remote_user }}"
line: "PAPERLESS_ENABLE_HTTP_REMOTE_USER={{ paperless_enable_http_remote_user }}"
# OCR settings
- regexp: PAPERLESS_OCR_LANGUAGE
line: "PAPERLESS_OCR_LANGUAGE={{ paperlessng_ocr_languages | join('+') }}"
@@ -391,20 +310,21 @@
creates: "{{ paperlessng_virtualenv }}"
register: venv
- block:
- name: install paperlessng requirements
become: yes
become_user: "{{ paperlessng_system_user }}"
pip:
requirements: "{{ paperlessng_directory }}/requirements.txt"
executable: "{{ paperlessng_virtualenv }}/bin/pip3"
extra_args: --upgrade
- name: migrate database schema
become: yes
become_user: "{{ paperlessng_system_user }}"
command: "{{ paperlessng_virtualenv }}/bin/python3 {{ paperlessng_directory }}/src/manage.py migrate"
register: database_schema
changed_when: '"No migrations to apply." not in database_schema.stdout'
- name: install paperlessng requirements
become: yes
become_user: "{{ paperlessng_system_user }}"
pip:
requirements: "{{ paperlessng_directory }}/requirements.txt"
executable: "{{ paperlessng_virtualenv }}/bin/pip3"
extra_args: --upgrade
when: not reconfigure_only
- name: migrate database schema
become: yes
become_user: "{{ paperlessng_system_user }}"
command: "{{ paperlessng_virtualenv }}/bin/python3 {{ paperlessng_directory }}/src/manage.py migrate"
register: database_schema
changed_when: '"No migrations to apply." not in database_schema.stdout'
when: not reconfigure_only
- name: configure paperless superuser
@@ -456,12 +376,6 @@
line: '\1<policy domain="coder" rights="read|write" pattern="PDF" />'
backrefs: yes
- name: configure gunicorn web server
lineinfile:
path: "{{ paperlessng_directory }}/gunicorn.conf.py"
regexp: '^bind = '
line: "bind = '{{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}'"
- name: configure systemd services
ini_file:
path: "{{ paperlessng_directory }}/scripts/{{ item[0] }}"
@@ -478,7 +392,7 @@
# https://www.freedesktop.org/software/systemd/man/systemd.exec.html
{ option: "User", value: "{{ paperlessng_system_user }}" },
{ option: "Group", value: "{{ paperlessng_system_group }}" },
{ option: "WorkingDirectory", value: "{{ paperlessng_directory }}/src" },
{ option: "WorkingDirectory", value: "{{ paperlessng_directory }}/src", },
{ option: "ProtectSystem", value: "full" },
{ option: "NoNewPrivileges", value: "true" },
{ option: "PrivateUsers", value: "true" },
@@ -504,7 +418,7 @@
path: "{{ paperlessng_directory }}/scripts/paperless-webserver.service"
section: "Service"
option: "ExecStart"
value: "{{ paperlessng_virtualenv }}/bin/gunicorn -c {{ paperlessng_directory }}/gunicorn.conf.py paperless.asgi:application"
value: "{{ paperlessng_virtualenv }}/bin/gunicorn paperless.wsgi -w 2 -b {{ paperlessng_listen_address }}:{{ paperlessng_listen_port }}"
- name: copy systemd services
copy:
+17 -3
View File
@@ -1,7 +1,21 @@
bind = '0.0.0.0:8000'
workers = 2
worker_class = 'uvicorn.workers.UvicornWorker'
timeout = 120
backlog = 2048
workers = 3
worker_class = 'sync'
worker_connections = 1000
timeout = 20
keepalive = 2
spew = False
daemon = False
pidfile = None
umask = 0
user = None
group = None
tmp_upload_dir = None
loglevel = 'info'
errorlog = '-'
accesslog = '-'
proc_name = None
def pre_fork(server, worker):
pass
-6
View File
@@ -1,6 +0,0 @@
for command in document_archiver document_exporter document_importer mail_fetcher document_create_classifier document_index document_renamer document_retagger document_thumbnails;
do
echo "installing $command..."
sed "s/management_command/$command/g" management_script.sh > /usr/local/bin/$command
chmod +x /usr/local/bin/$command
done
-15
View File
@@ -1,15 +0,0 @@
#!/bin/bash
set -e
cd /usr/src/paperless/src/
if [[ $(id -u) == 0 ]] ;
then
sudo -HEu paperless python3 manage.py management_command "$@"
elif [[ $(id -un) == "paperless" ]] ;
then
python3 manage.py management_command "$@"
else
echo "Unknown user."
fi
+1 -1
View File
@@ -8,7 +8,7 @@ loglevel=info ; log level; default info; others: debug,warn,trace
user=root
[program:gunicorn]
command=gunicorn -c /usr/src/paperless/gunicorn.conf.py paperless.asgi:application
command=gunicorn -c /usr/src/paperless/gunicorn.conf.py paperless.wsgi
user=paperless
stdout_logfile=/dev/stdout
+9 -52
View File
@@ -23,12 +23,6 @@ Options available to any installation of paperless:
* The document exporter is also able to update an already existing export.
Therefore, incremental backups with ``rsync`` are entirely possible.
.. caution::
You cannot import the export generated with one version of paperless in a
different version of paperless. The export contains an exact image of the
database, and migrations may change the database layout.
Options available to docker installations:
* Backup the docker volumes. These usually reside within
@@ -107,17 +101,17 @@ Then you can start paperless-ng with ``-d`` to have it run in the background.
update to newer versions. In order to enable updates as described above, either
get the new ``docker-compose.yml`` file from `here <https://github.com/jonaswinkler/paperless-ng/tree/master/docker/compose>`_
or edit the ``docker-compose.yml`` file, find the line that says
.. code::
image: jonaswinkler/paperless-ng:0.9.x
and replace the version with ``latest``:
.. code::
image: jonaswinkler/paperless-ng:latest
Bare Metal Route
================
@@ -177,63 +171,26 @@ Most of the update process is automated when using the ansible role.
$ ansible-playbook playbook.yml
Downgrading Paperless
#####################
Downgrades are possible. However, some updates also contain database migrations (these change the layout of the database and may move data).
In order to move back from a version that applied database migrations, you'll have to revert the database migration *before* downgrading,
and then downgrade paperless.
This table lists the most recent database migrations for each versions:
+---------+-------------------------+
| Version | Latest migration number |
+---------+-------------------------+
| 1.0.0 | 1011 |
+---------+-------------------------+
| 1.1.0 | 1011 |
+---------+-------------------------+
| 1.1.1 | 1012 |
+---------+-------------------------+
Execute the following management command to migrate your database:
.. code:: shell-session
$ python3 manage.py migrate documents <migration number>
.. note::
Some migrations cannot be undone. The command will issue errors if that happens.
.. _utilities-management-commands:
Management utilities
####################
Paperless comes with some management commands that perform various maintenance
tasks on your paperless instance. You can invoke these commands in the following way:
With docker-compose, while paperless is running:
tasks on your paperless instance. You can invoke these commands either by
.. code:: shell-session
$ cd /path/to/paperless
$ docker-compose exec webserver <command> <arguments>
$ docker-compose run --rm webserver <command> <arguments>
With docker, while paperless is running:
.. code:: shell-session
$ docker exec -it <container-name> <command> <arguments>
Bare metal:
or
.. code:: shell-session
$ cd /path/to/paperless/src
$ python3 manage.py <command> <arguments>
depending on whether you use docker or not.
All commands have built-in help, which can be accessed by executing them with
the argument ``--help``.
@@ -253,7 +210,7 @@ backup or migration to another DMS.
-c, --compare-checksums
-f, --use-filename-format
-d, --delete
``target`` is a folder to which the data gets written. This includes documents,
thumbnails and a ``manifest.json`` file. The manifest contains all metadata from
the database (correspondents, tags, etc).
-1
View File
@@ -217,7 +217,6 @@ will create a directory structure as follows:
Paperless provides the following placeholders withing filenames:
* ``{asn}``: The archive serial number of the document, or "none".
* ``{correspondent}``: The name of the correspondent, or "none".
* ``{document_type}``: The name of the document type, or "none".
* ``{tag_list}``: A comma separated list of all tags assigned to the document.
-81
View File
@@ -5,87 +5,6 @@
Changelog
*********
paperless-ng 1.1.1
##################
This release contains new database migrations.
* Fixed a bug in the sanity checker that would cause it to display "x not in list" errors instead of actual issues.
* Fixed a bug with filename generation for archive filenames that would cause the archive files of two documents to overlap.
* This happened when ``PAPERLESS_FILENAME_FORMAT`` is used and the filenames of two or more documents are the same, except for the file extension.
* Paperless will now store the archive filename in the database as well instead of deriving it from the original filename, and use the
same logic for detecting and avoiding filename clashes that's also used for original filenames.
* The migrations will repair any missing archive files. If you're using tika, ensure that tika is running while performing the migration. Docker-compose will take care of that.
* Fixed a bug with thumbnail regeneration when TIKA integration was used.
* Added ASN as a placeholder field to the filename format.
* The docker image now comes with built-in shortcuts for most management commands. These are now the recommended way to execute management commands, since these
also ensure that they're always executed as the paperless user and you're less likely to run into permission issues. See :ref:`utilities-management-commands`.
paperless-ng 1.1.0
##################
* Document processing status
* Paperless now shows the status of processing documents on the dashboard in real time.
* Status notifications when
* New documents are detected in the consumption folder, in mails, uploaded on the front end,
or added with one of the mobile apps.
* Documents are successfully added to paperless.
* Document consumption failed (with error messages)
* Configuration options to enable/disable individual notifications.
* Live updates to document lists and saved views when new documents are added.
.. hint::
For status notifications and live updates to work, paperless now requires an `ASGI <https://asgi.readthedocs.io/en/latest/>`_-enabled
web server. The docker images uses ``gunicorn`` and an ASGI-enabled worker called `uvicorn <http://www.uvicorn.org/>`_,
and there is no need to configure anything.
For bare metal installations, changes are required for the notifications to work. Adapt the service ``paperless-webserver.service``
to use the supplied ``gunicorn.conf.py`` configuration file and adapt the reference to the ASGI application as follows:
.. code::
ExecStart=/opt/paperless/.local/bin/gunicorn -c /opt/paperless/gunicorn.conf.py paperless.asgi:application
Paperless will continue to work with WSGI, but you will not get any status notifications.
Apache ``mod_wsgi`` users, see :ref:`this note <faq-mod_wsgi>`.
* Paperless now offers suggestions for tags, correspondents and types on the document detail page.
* Added an interactive easy install script that automatically downloads, configures and starts paperless with docker.
* Official support for Python 3.9.
* Other changes and fixes
* Adjusted the default parallelization settings to run more than one task in parallel on systems with 4 or less cores.
This addresses issues with paperless not consuming any new files when other tasks are running.
* Fixed a rare race condition that would cause paperless to process incompletely written files when using the upload on the dashboard.
* The document classifier no longer issues warnings and errors when auto matching is not used at all.
* Better icon for document previews.
* Better info section in the side bar.
* Paperless no longer logs to the database. Instead, logs are written to rotating log files. This solves many "database is locked"
issues on Raspberry Pi, especially when SQLite is used.
* By default, log files are written to ``PAPERLESS_DATA_DIR/log/``. Logging settings can be adjusted with
``PAPERLESS_LOGGING_DIR``, ``PAPERLESS_LOGROTATE_MAX_SIZE`` and
``PAPERLESS_LOGROTATE_MAX_BACKUPS``.
paperless-ng 1.0.0
##################
+1 -1
View File
@@ -28,7 +28,7 @@ master_doc = 'index'
# General information about the project.
project = u'Paperless-ng'
copyright = u'2021, Daniel Quinn, Jonas Winkler'
copyright = u'2015, Daniel Quinn'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
+4 -23
View File
@@ -72,13 +72,13 @@ PAPERLESS_CONSUMPTION_DIR=<path>
container. Change the local consumption directory in the docker-compose.yml
file instead.
Defaults to "../consume/", relative to the "src" directory.
Defaults to "../consume", relative to the "src" directory.
PAPERLESS_DATA_DIR=<path>
This is where paperless stores all its data (search index, SQLite database,
classification model, etc).
Defaults to "../data/", relative to the "src" directory.
Defaults to "../data", relative to the "src" directory.
PAPERLESS_MEDIA_ROOT=<path>
This is where your documents and thumbnails are stored.
@@ -86,7 +86,7 @@ PAPERLESS_MEDIA_ROOT=<path>
You can set this and PAPERLESS_DATA_DIR to the same folder to have paperless
store all its data within the same volume.
Defaults to "../media/", relative to the "src" directory.
Defaults to "../media", relative to the "src" directory.
PAPERLESS_STATICDIR=<path>
Override the default STATIC_ROOT here. This is where all static files
@@ -94,7 +94,7 @@ PAPERLESS_STATICDIR=<path>
Unless you're doing something fancy, there is no need to override this.
Defaults to "../static/", relative to the "src" directory.
Defaults to "../static", relative to the "src" directory.
PAPERLESS_FILENAME_FORMAT=<format>
Changes the filenames paperless uses to store documents in the media directory.
@@ -102,25 +102,6 @@ PAPERLESS_FILENAME_FORMAT=<format>
Default is none, which disables this feature.
PAPERLESS_LOGGING_DIR=<path>
This is where paperless will store log files.
Defaults to "``PAPERLESS_DATA_DIR``/log/".
Logging
#######
PAPERLESS_LOGROTATE_MAX_SIZE=<num>
Maximum file size for log files before they are rotated, in bytes.
Defaults to 1 MiB.
PAPERLESS_LOGROTATE_MAX_BACKUPS=<num>
Number of rotated log files to keep.
Defaults to 20.
Hosting & Security
##################
+10 -26
View File
@@ -62,9 +62,9 @@ file extensions do not matter.
**A:** The short answer is yes. I've tested it on a Raspberry Pi 3 B.
The long answer is that certain parts of
Paperless will run very slow, such as the OCR. On Raspberry Pi,
Paperless will run very slow, such as the tesseract OCR. On Raspberry Pi,
try to OCR documents before feeding them into paperless so that paperless can
reuse the text. The web interface is a lot snappier, since it runs
reuse the text. The web interface should be a lot snappier, since it runs
in your browser and paperless has to do much less work to serve the data.
.. note::
@@ -76,14 +76,7 @@ in your browser and paperless has to do much less work to serve the data.
**Q:** *How do I install paperless-ng on Raspberry Pi?*
**A:** Docker images are available for arm and arm64 hardware, so just follow
the docker-compose instructions. Apart from more required disk space compared to
a bare metal installation, docker comes with close to zero overhead, even on
Raspberry Pi.
If you decide to got with the bare metal route, be aware that some of the
python requirements do not have precompiled packages for ARM / ARM64. Installation
of these will require additional development libraries and compilation will take
a long time.
the docker-compose instructions, or go the bare metal route.
**Q:** *How do I run this on unRaid?*
@@ -102,21 +95,12 @@ occasionally build the image on and see if it works.
**Q:** *How do I proxy this with NGINX?*
**A:** See :ref:`here <setup-nginx>`.
.. code::
.. _faq-mod_wsgi:
location / {
proxy_pass http://localhost:8000/
}
**Q:** *How do I get WebSocket support with Apache mod_wsgi*?
**A:** ``mod_wsgi`` by itself does not support ASGI. Paperless will continue
to work with WSGI, but certain features such as status notifications about
document consumption won't be available.
If you want to continue using ``mod_wsgi``, you will have to run an ASGI-enabled
web server as well that processes WebSocket connections, and configure Apache to
redirect WebSocket connections to this server. Multiple options for ASGI servers
exist:
* ``gunicorn`` with ``uvicorn`` as the worker implementation (the default of paperless)
* ``daphne`` as a standalone server, which is the reference implementation for ASGI.
* ``uvicorn`` as a standalone server
And that's about it. Paperless serves everything, including static files by itself
when running the docker image. If you want to do anything fancy, you have to
install paperless bare metal.
+1 -1
View File
@@ -70,8 +70,8 @@ Contents
configuration
api
faq
troubleshooting
extending
troubleshooting
contributing
scanners
screenshots
+55 -131
View File
@@ -20,7 +20,7 @@ Paperless consists of the following components:
.. code:: shell-session
$ cd /path/to/paperless/src/
$ gunicorn -c ../gunicorn.conf.py paperless.wsgi
$ gunicorn -c ../gunicorn.conf.py -b 0.0.0.0:8000 paperless.wsgi
or by any other means such as Apache ``mod_wsgi``.
@@ -81,7 +81,6 @@ Installation
You can go multiple routes to setup and run Paperless:
* :ref:`Use the easy install docker script <setup-docker_script>`
* :ref:`Pull the image from Docker Hub <setup-docker_hub>`
* :ref:`Build the Docker image yourself <setup-docker_build>`
* :ref:`Install Paperless directly on your system manually (bare metal) <setup-bare_metal>`
@@ -89,7 +88,7 @@ You can go multiple routes to setup and run Paperless:
The Docker routes are quick & easy. These are the recommended routes. This configures all the stuff
from the above automatically so that it just works and uses sensible defaults for all configuration options.
Here you find a cheat-sheet for docker beginners: `CLI Basics <https://sehn.tech/post/devops-with-docker/>`_
Here you find a cheat-sheet for docker beginners: `CLI Basics <https://sehn.tech/post/devops-with-docker/>`_
The bare metal route is complicated to setup but makes it easier
should you want to contribute some code back. You need to configure and
@@ -102,24 +101,6 @@ it includes the same sensible defaults, and it simultaneously provides the flexi
.. _CLI Basics: https://sehn.tech/post/devops-with-docker/
.. _idempotent: https://docs.ansible.com/ansible/latest/reference_appendices/glossary.html#Idempotency
.. _setup-docker_script:
Install Paperless from Docker Hub using the installation script
===============================================================
Paperless provides an interactive installation script. This script will ask you
for a couple configuration options, download and create the necessary configuration files, pull the docker image, start paperless and create your user account. This script essentially
performs all the steps described in :ref:`setup-docker_hub` automatically.
1. Make sure that docker and docker-compose are installed.
2. Download and run the installation script:
.. code:: shell-session
$ wget https://raw.githubusercontent.com/jonaswinkler/paperless-ng/master/install-paperless-ng.sh
$ chmod +x install-paperless-ng.sh
$ ./install-paperless-ng.sh
.. _setup-docker_hub:
Install Paperless from Docker Hub
@@ -145,7 +126,7 @@ Install Paperless from Docker Hub
If you want to use the included ``docker-compose.*.yml`` file, you
need to have at least Docker version **17.09.0** and docker-compose
version **1.17.0**.
version **1.17.0**.
To check do: `docker-compose -v` or `docker -v`
See the `Docker installation guide`_ on how to install the current
@@ -176,10 +157,7 @@ Install Paperless from Docker Hub
5. Modify ``docker-compose.env``, following the comments in the file. The
most important change is to set ``USERMAP_UID`` and ``USERMAP_GID``
to the uid and gid of your user on the host system. Use ``id -u`` and
``id -g`` to get these.
This ensures that
to the uid and gid of your user on the host system. This ensures that
both the docker container and you on the host machine have write access
to the consumption directory. If your UID and GID on the host system is
1000 (the default for the first normal user on most systems), it will
@@ -197,11 +175,15 @@ Install Paperless from Docker Hub
on such a file system, paperless will not pick up new files
with the default configuration. You will need to use ``PAPERLESS_CONSUMER_POLLING``,
which will disable inotify. See :ref:`here <configuration-polling>`.
6. Now head over to: https://hub.docker.com/r/jonaswinkler/paperless-ng and choose your preferred
image and copy the link. To download this image do a `docker pull` followed by the link. Do this within the directory with the .yml files.
Depending on your network connection and CPU this will take a while. You have time to get a beverage.
6. Run ``docker-compose pull``, followed by ``docker-compose up -d``.
This will pull the image, create and start the necessary containers.
7. Run ``docker-compose up -d``. This will create and start the necessary
containers, but your are not done yet!
7. To be able to login, you will need a super user. To create it, execute the
8. To be able to login, you will need a super user. To create it, execute the
following command:
.. code-block:: shell-session
@@ -211,9 +193,9 @@ Install Paperless from Docker Hub
This will prompt you to set a username, an optional e-mail address and
finally a password (at least 8 characters).
8. The default ``docker-compose.yml`` exports the webserver on your local port
8000. If you did not change this, you should now be able to visit your
Paperless instance at ``http://127.0.0.1:8000`` or your servers IP-Address:8000.
9. The default ``docker-compose.yml`` exports the webserver on your local port
8000. If you haven't adapted this, you should now be able to visit your
Paperless instance at ``http://127.0.0.1:8000`` or your servers IP-Address:8000.
Use the login credentials you have created with the previous step.
.. _Docker: https://www.docker.com/
@@ -221,7 +203,7 @@ Install Paperless from Docker Hub
.. _setup-docker_build:
Build the Docker image yourself
Build the docker image yourself
===============================
1. Clone the entire repository of paperless:
@@ -242,7 +224,7 @@ Build the Docker image yourself
webserver:
image: jonaswinkler/paperless-ng:latest
and replace it with a line that instructs docker-compose to build the image from the current working directory instead:
.. code:: yaml
@@ -252,14 +234,14 @@ Build the Docker image yourself
4. Run the ``compile-frontend.sh`` script. This requires ``node`` and ``npm >= v15``.
5. Follow steps 3 to 8 of :ref:`setup-docker_hub`. When asked to run
``docker-compose pull`` to pull the image, do
5. Follow steps 2 to 7 of :ref:`setup-docker_hub`. When asked to run
``docker-compose up -d`` to start the containers, do
.. code:: shell-session
$ docker-compose build
instead to build the image.
before that to build the image.
.. _setup-bare_metal:
@@ -272,7 +254,7 @@ writing. Windows is not and will never be supported.
1. Install dependencies. Paperless requires the following packages.
* ``python3`` 3.6, 3.7, 3.8, 3.9
* ``python3`` 3.6, 3.7, 3.8 (3.9 is untested).
* ``python3-pip``
* ``python3-dev``
@@ -309,7 +291,7 @@ writing. Windows is not and will never be supported.
2. Install ``redis`` >= 5.0 and configure it to start automatically.
3. Optional. Install ``postgresql`` and configure a database, user and password for paperless. If you do not wish
to use PostgreSQL, SQLite is available as well.
to use PostgreSQL, SQLite is avialable as well.
4. Get the release archive from `<https://github.com/jonaswinkler/paperless-ng/releases>`_.
If you clone the git repo as it is, you also have to compile the front end by yourself.
@@ -333,14 +315,8 @@ writing. Windows is not and will never be supported.
* Set ``PAPERLESS_OCR_LANGUAGE`` to the language most of your documents are written in.
* Set ``PAPERLESS_TIME_ZONE`` to your local time zone.
6. Create a system user under which you wish to run paperless.
.. code:: shell-session
adduser paperless --system --home /opt/paperless --group
7. Ensure that these directories exist
and that the paperless user has write permissions to the following directories:
6. Setup permissions. Create a system users under which you wish to run paperless. Ensure that these directories exist
and that the user has write permissions to the following directories
* ``/opt/paperless/media``
* ``/opt/paperless/data``
@@ -348,32 +324,30 @@ writing. Windows is not and will never be supported.
Adjust as necessary if you configured different folders.
8. Install python requirements from the ``requirements.txt`` file.
7. Install python requirements from the ``requirements.txt`` file.
It is up to you if you wish to use a virtual environment or not.
.. code:: shell-session
sudo -Hu paperless pip3 install -r requirements.txt
This will install all python dependencies in the home directory of
the new paperless user.
pip3 install -r requirements.txt
9. Go to ``/opt/paperless/src``, and execute the following commands:
8. Go to ``/opt/paperless/src``, and execute the following commands:
.. code:: bash
# This creates the database schema.
sudo -Hu paperless python3 manage.py migrate
python3 manage.py migrate
# This creates your first paperless user
sudo -Hu paperless python3 manage.py createsuperuser
python3 manage.py createsuperuser
10. Optional: Test that paperless is working by executing
9. Optional: Test that paperless is working by executing
.. code:: bash
# This collects static files from paperless and django.
sudo -Hu paperless python3 manage.py runserver
python3 manage.py runserver
and pointing your browser to http://localhost:8000/.
@@ -388,7 +362,7 @@ writing. Windows is not and will never be supported.
This will not start the consumer. Paperless does this in a
separate process.
11. Setup systemd services to run paperless automatically. You may
10. Setup systemd services to run paperless automatically. You may
use the service definition files included in the ``scripts`` folder
as a starting point.
@@ -404,7 +378,14 @@ writing. Windows is not and will never be supported.
These services rely on redis and optionally the database server, but
don't need to be started in any particular order. The example files
depend on redis being started. If you use a database server, you should
add additional dependencies.
add additinal dependencies.
.. hint::
You may optionally set up your preferred web server to serve
paperless as a wsgi application directly instead of running the
``webserver`` service. The module containing the wsgi application
is named ``paperless.wsgi``.
.. caution::
@@ -413,13 +394,10 @@ writing. Windows is not and will never be supported.
however, the documentation of GUnicorn states that you should
use a proxy server in front of gunicorn instead.
For instructions on how to use nginx for that,
:ref:`see the instructions below <setup-nginx>`.
12. Optional: Install a samba server and make the consumption folder
11. Optional: Install a samba server and make the consumption folder
available as a network share.
13. Configure ImageMagick to allow processing of PDF documents. Most distributions have
12. Configure ImageMagick to allow processing of PDF documents. Most distributions have
this disabled by default, since PDF documents can contain malware. If
you don't do this, paperless will fall back to ghostscript for certain steps
such as thumbnail generation.
@@ -436,7 +414,7 @@ writing. Windows is not and will never be supported.
<policy domain="coder" rights="read|write" pattern="PDF" />
14. Optional: Install the `jbig2enc <https://ocrmypdf.readthedocs.io/en/latest/jbig2.html>`_
13. Optional: Install the `jbig2enc <https://ocrmypdf.readthedocs.io/en/latest/jbig2.html>`_
encoder. This will reduce the size of generated PDF documents. You'll most likely need
to compile this by yourself, because this software has been patented until around 2017 and
binary packages are not available for most distributions.
@@ -445,12 +423,11 @@ writing. Windows is not and will never be supported.
Install Paperless using ansible
===============================
.. note::
.. note::
This role currently only supports Debian 10 Buster and Ubuntu 20.04 Focal or later as target hosts.
This role currently only supports Debian 10 Buster and Ubuntu 20.04 Focal or later as target hosts.
1. Install ansible 2.7+ on the management node.
1. Install ansible 2.7+ on the management node.
This may be the target host paperless-ng is being installed on or any remote host which can access the target host.
For further details, check the ansible `inventory <https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html>`_ documentation.
@@ -485,7 +462,7 @@ Install Paperless using ansible
.. code:: sh
cd paperless-ng
git checkout ng-1.0.0
git checkout ng-0.9.14
3. Create an ansible ``playbook.yml`` in the paperless-ng root directory:
@@ -535,7 +512,7 @@ Install Paperless using ansible
.. code:: yaml
paperlessng_secret_key: PleaseGenerateAStrongKeyForThis
paperless_secret_key: PleaseGenerateAStrongKeyForThis
paperlessng_superuser_name: YourUserName
paperlessng_superuser_email: name@domain.tld
@@ -562,10 +539,7 @@ Migration to paperless-ng
At its core, paperless-ng is still paperless and fully compatible. However, some
things have changed under the hood, so you need to adapt your setup depending on
how you installed paperless.
This setup describes how to update an existing paperless Docker installation.
The important things to keep in mind are as follows:
how you installed paperless. The important things to keep in mind are as follows.
* Read the :ref:`changelog <paperless_changelog>` and take note of breaking changes.
* You should decide if you want to stick with SQLite or want to migrate your database
@@ -593,25 +567,18 @@ Migration to paperless-ng is then performed in a few simple steps:
paperless.
3. Download the latest release of paperless-ng. You can either go with the
docker-compose files from `here <https://github.com/jonaswinkler/paperless-ng/tree/master/docker/compose>`__
docker-compose files from `here <https://github.com/jonaswinkler/paperless-ng/tree/master/docker/compose>`_
or clone the repository to build the image yourself (see :ref:`above <setup-docker_build>`).
You can either replace your current paperless folder or put paperless-ng
in a different location.
.. caution::
Paperless-ng includes a ``.env`` file. This will set the
project name for docker compose to ``paperless``, which will also define the name
of the volumes by paperless-ng. However, if you experience that paperless-ng
is not using your old paperless volumes, verify the names of your volumes with
.. code:: shell-session
$ docker volume ls | grep _data
and adjust the project name in the ``.env`` file so that it matches the name
of the volumes before the ``_data`` part.
Paperless includes a ``.env`` file. This will set the
project name for docker compose to ``paperless`` so that paperless-ng will
automatically reuse your existing paperless volumes. When you start it, it
will migrate your existing data. After that, your old paperless installation
will be incompatible with the migrated volumes.
4. Download the ``docker-compose.sqlite.yml`` file to ``docker-compose.yml``.
If you want to switch to PostgreSQL, do that after you migrated your existing
@@ -792,46 +759,3 @@ For details, refer to :ref:`configuration`.
well as on any other device.
.. _redis: https://redis.io/
.. _setup-nginx:
Using nginx as a reverse proxy
##############################
If you want to expose paperless to the internet, you should hide it behind a
reverse proxy with SSL enabled.
In addition to the usual configuration for SSL,
the following configuration is required for paperless to operate:
.. code:: nginx
http {
# Adjust as required. This is the maximum size for file uploads.
# The default value 1M might be a little too small.
client_max_body_size 10M;
server {
location / {
# Adjust host and port as required.
proxy_pass http://localhost:8000/;
# These configuration options are required for WebSockets to work.
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
}
Also read `this <https://channels.readthedocs.io/en/stable/deploying.html#nginx-supervisor-ubuntu>`__, towards the end of the section.
+2 -37
View File
@@ -60,8 +60,7 @@ required so that the user running paperless inside docker has write permissions
to these folders. This happens when pointing these directories to NFS shares,
for example.
Ensure that ``chown`` is possible on these directories.
Ensure that `chown` is possible on these directories.
Classifier error: No training data available
############################################
@@ -74,7 +73,6 @@ This may have two reasons:
with Inbox tags. Verify that there are documents in your archive without inbox tags.
The algorithm will only learn from documents not in your inbox.
UserWarning in sklearn on every single document
###############################################
@@ -93,7 +91,6 @@ in most cases. This warning will disappear automatically when paperless updates
If you want to get rid of the warning or actually experience issues with automatic matching, delete
the file ``classification_model.pickle`` in the data directory and let paperless recreate it.
Permission denied errors in the consumption directory
#####################################################
@@ -109,38 +106,6 @@ different from ``1000``. See :ref:`setup-docker_hub`.
Also ensure that you are able to read and write to the consumption directory on the host.
OSError: [Errno 19] No such device when consuming files
#######################################################
If you experience errors such as:
.. code:: shell-session
File "/usr/local/lib/python3.7/site-packages/whoosh/codec/base.py", line 570, in open_compound_file
return CompoundStorage(dbfile, use_mmap=storage.supports_mmap)
File "/usr/local/lib/python3.7/site-packages/whoosh/filedb/compound.py", line 75, in __init__
self._source = mmap.mmap(fileno, 0, access=mmap.ACCESS_READ)
OSError: [Errno 19] No such device
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/django_q/cluster.py", line 436, in worker
res = f(*task["args"], **task["kwargs"])
File "/usr/src/paperless/src/documents/tasks.py", line 73, in consume_file
override_tag_ids=override_tag_ids)
File "/usr/src/paperless/src/documents/consumer.py", line 271, in try_consume_file
raise ConsumerError(e)
Paperless uses a search index to provide better and faster full text searching. This search index is stored inside
the ``data`` folder. The search index uses memory-mapped files (mmap). The above error indicates that paperless
was unable to create and open these files.
This happens when you're trying to store the data directory on certain file systems (mostly network shares)
that don't support memory-mapped files.
Web-UI stuck at "Loading..."
############################
@@ -183,4 +148,4 @@ This might have multiple reasons.
SENDFILE=0
to your `docker-compose.env` file.
to your `docker-compose.env` file.
-295
View File
@@ -1,295 +0,0 @@
#!/bin/bash
set -e
ask() {
while true ; do
if [[ -z $3 ]] ; then
read -p "$1 [$2]: " result
else
read -p "$1 ($3) [$2]: " result
fi
if [[ -z $result ]]; then
ask_result=$2
return
fi
array=$3
if [[ -z $3 || " ${array[@]} " =~ " ${result} " ]]; then
ask_result=$result
return
else
echo "Invalid option: $result"
fi
done
}
ask_docker_folder() {
while true ; do
read -p "$1 [$2]: " result
if [[ -z $result ]]; then
ask_result=$2
return
fi
if [[ $result == /* || $result == ./* ]]; then
ask_result=$result
return
else
echo "Invalid folder: $result"
fi
done
}
if [[ $(id -u) == "0" ]] ; then
echo "Do not run this script as root."
exit 1
fi
if [[ -z $(which wget) ]] ; then
echo "wget executable not found. Is wget installed?"
exit 1
fi
if [[ -z $(which docker) ]] ; then
echo "docker executable not found. Is docker installed?"
exit 1
fi
if [[ -z $(which docker-compose) ]] ; then
echo "docker-compose executable not found. Is docker-compose installed?"
exit 1
fi
echo ""
echo "############################################"
echo "### Paperless-ng docker installation ###"
echo "############################################"
echo ""
echo "This script will download, configure and start paperless-ng."
echo ""
echo "1. Folder configuration"
echo "======================="
echo ""
echo "The target folder is used to store the configuration files of "
echo "paperless. You can move this folder around after installing paperless."
echo "You will need this folder whenever you want to start, stop, update or "
echo "maintain your paperless instance."
echo ""
ask "Target folder" "$(pwd)/paperless-ng"
TARGET_FOLDER=$ask_result
echo ""
echo "The consume folder is where paperles will search for new documents."
echo "Point this to a folder where your scanner is able to put your scanned"
echo "documents."
echo ""
echo "CAUTION: You must specify an absolute path starting with / or a relative "
echo "path starting with ./ here. Examples:"
echo " /mnt/consume"
echo " ./consume"
echo ""
ask_docker_folder "Consume folder" "$TARGET_FOLDER/consume"
CONSUME_FOLDER=$ask_result
echo ""
echo "The media folder is where paperless stores your documents."
echo "Leave empty and docker will manage this folder for you."
echo "Docker usually stores managed folders in /var/lib/docker/volumes."
echo ""
echo "CAUTION: If specified, you must specify an absolute path starting with /"
echo "or a relative path starting with ./ here."
echo ""
ask_docker_folder "Media folder" ""
MEDIA_FOLDER=$ask_result
echo ""
echo "The data folder is where paperless stores other data, such as your"
echo "SQLite database (if used), the search index and other data."
echo "As with the media folder, leave empty to have this managed by docker."
echo ""
echo "CAUTION: If specified, you must specify an absolute path starting with /"
echo "or a relative path starting with ./ here."
echo ""
ask_docker_folder "Data folder" ""
DATA_FOLDER=$ask_result
echo ""
echo "2. Application configuration"
echo "============================"
echo ""
echo "The port on which the paperless webserver will listen for incoming"
echo "connections."
echo ""
ask "Port" "8000"
PORT=$ask_result
echo ""
echo "Database backend: PostgreSQL and SQLite are available. Use PostgreSQL"
echo "if unsure. If you're running on a low-power device such as Raspberry"
echo "Pi, use SQLite to save resources."
echo ""
ask "Database backend" "postgres" "postgres sqlite"
DATABASE_BACKEND=$ask_result
echo ""
echo "Paperless is able to use Apache Tika to support Office documents such as"
echo "Word, Excel, Powerpoint, and Libreoffice equivalents. This feature"
echo "requires more resources due to the required services."
echo ""
ask "Enable Apache Tika?" "no" "yes no"
TIKA_ENABLED=$ask_result
echo ""
echo "Specify the default language that most of your documents are written in."
echo "Use ISO 639-2, (T) variant language codes: "
echo "https://www.loc.gov/standards/iso639-2/php/code_list.php"
echo "Common values: eng (English) deu (German) nld (Dutch) fra (French)"
echo ""
ask "OCR language" "eng"
OCR_LANGUAGE=$ask_result
echo ""
echo "Specify the user id and group id you wish to run paperless as."
echo "Paperless will also change ownership on the data, media and consume"
echo "folder to the specified values, so it's a good idea to supply the user id"
echo "and group id of your unix user account."
echo "If unsure, leave default."
echo ""
ask "User ID" "$(id -u)"
USERMAP_UID=$ask_result
ask "Group ID" "$(id -g)"
USERMAP_GID=$ask_result
echo ""
echo "3. Login credentials"
echo "===================="
echo ""
echo "Specify initial login credentials. You can change these later."
echo "A mail address is required, however it is not used in paperless. You don't"
echo "need to provide an actual mail address."
echo ""
ask "Paperless username" "$(whoami)"
USERNAME=$ask_result
while true; do
read -sp "Paperless password: " PASSWORD
echo ""
if [[ -z $PASSWORD ]] ; then
echo "Password cannot be empty."
continue
fi
read -sp "Paperless password (again): " PASSWORD_REPEAT
echo ""
if [[ ! "$PASSWORD" == "$PASSWORD_REPEAT" ]] ; then
echo "Passwords did not match"
else
break
fi
done
ask "Email" "$USERNAME@localhost"
EMAIL=$ask_result
echo ""
echo "Summary"
echo "======="
echo ""
echo "Target folder: $TARGET_FOLDER"
echo "Consume folder: $CONSUME_FOLDER"
if [[ -z $MEDIA_FOLDER ]] ; then
echo "Media folder: Managed by docker"
else
echo "Media folder: $MEDIA_FOLDER"
fi
if [[ -z $DATA_FOLDER ]] ; then
echo "Data folder: Managed by docker"
else
echo "Data folder: $DATA_FOLDER"
fi
echo ""
echo "Port: $PORT"
echo "Database: $DATABASE_BACKEND"
echo "Tika enabled: $TIKA_ENABLED"
echo "OCR language: $OCR_LANGUAGE"
echo "User id: $USERMAP_UID"
echo "Group id: $USERMAP_GID"
echo ""
echo "Paperless username: $USERNAME"
echo "Paperless email: $EMAIL"
echo ""
read -p "Press any key to install."
echo ""
echo "Installing paperless..."
echo ""
mkdir -p "$TARGET_FOLDER"
cd "$TARGET_FOLDER"
DOCKER_COMPOSE_VERSION=$DATABASE_BACKEND
if [[ $TIKA_ENABLED == "yes" ]] ; then
DOCKER_COMPOSE_VERSION="$DOCKER_COMPOSE_VERSION-tika"
fi
wget "https://raw.githubusercontent.com/jonaswinkler/paperless-ng/master/docker/compose/docker-compose.$DOCKER_COMPOSE_VERSION.yml" -O docker-compose.yml
wget "https://raw.githubusercontent.com/jonaswinkler/paperless-ng/master/docker/compose/.env" -O .env
SECRET_KEY=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 64 | head -n 1)
DEFAULT_LANGUAGES="deu eng fra ita spa"
{
if [[ ! $USERMAP_UID == "1000" ]] ; then
echo "USERMAP_UID=$USERMAP_UID"
fi
if [[ ! $USERMAP_GID == "1000" ]] ; then
echo "USERMAP_GID=$USERMAP_GID"
fi
echo "PAPERLESS_OCR_LANGUAGE=$OCR_LANGUAGE"
echo "PAPERLESS_SECRET_KEY=$SECRET_KEY"
if [[ ! " ${DEFAULT_LANGUAGES[@]} " =~ " ${OCR_LANGUAGE} " ]] ; then
echo "PAPERLESS_OCR_LANGUAGES=$OCR_LANGUAGE"
fi
} > docker-compose.env
sed -i "s/- 8000:8000/- $PORT:8000/g" docker-compose.yml
sed -i "s#- \./consume:/usr/src/paperless/consume#- $CONSUME_FOLDER:/usr/src/paperless/consume#g" docker-compose.yml
if [[ -n $MEDIA_FOLDER ]] ; then
sed -i "s#- media:/usr/src/paperless/media#- $MEDIA_FOLDER:/usr/src/paperless/media#g" docker-compose.yml
fi
if [[ -n $DATA_FOLDER ]] ; then
sed -i "s#- data:/usr/src/paperless/data#- $DATA_FOLDER:/usr/src/paperless/data#g" docker-compose.yml
fi
docker-compose pull
docker-compose run --rm -e DJANGO_SUPERUSER_PASSWORD="$PASSWORD" webserver createsuperuser --noinput --username "$USERNAME" --email "$EMAIL"
docker-compose up -d
+18 -47
View File
@@ -7,97 +7,68 @@
-i https://pypi.python.org/simple
--extra-index-url https://www.piwheels.org/simple
aioredis==1.3.1
arrow==0.17.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
asgiref==3.3.1; python_version >= '3.5'
async-timeout==3.0.1; python_full_version >= '3.5.3'
attrs==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
autobahn==21.1.1; python_version >= '3.6'
automat==20.2.0
blessed==1.17.12
certifi==2020.12.5
cffi==1.14.4
channels-redis==3.2.0
channels==3.0.3
chardet==4.0.0; python_version >= '3.1'
click==7.1.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
coloredlogs==15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
concurrent-log-handler==0.9.19
constantly==15.1.0
cryptography==3.3.2
daphne==3.0.1; python_version >= '3.6'
cryptography==3.3.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
dateparser==0.7.6
django-cors-headers==3.7.0
django-extensions==3.1.1
django-cors-headers==3.6.0
django-extensions==3.1.0
django-filter==2.4.0
django-picklefield==3.0.1; python_version >= '3'
django-q==1.3.4
django-redis==4.12.1
django==3.1.6
django==3.1.5
djangorestframework==3.12.2
filelock==3.0.12
fuzzywuzzy[speedup]==0.18.0
fuzzywuzzy==0.18.0
gunicorn==20.0.4
h11==0.12.0; python_version >= '3.6'
hiredis==1.1.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
httptools==0.1.1
humanfriendly==9.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
hyperlink==21.0.0
idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
imap-tools==0.37.0
imap-tools==0.34.0
img2pdf==0.4.0
incremental==17.5.0
importlib-metadata==3.4.0; python_version < '3.8'
inotify-simple==1.3.5; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
inotifyrecursive==0.3.5
joblib==1.0.1; python_version >= '3.6'
joblib==1.0.0; python_version >= '3.6'
langdetect==1.0.8
lxml==4.6.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
msgpack==1.0.2
numpy==1.19.5
ocrmypdf==11.6.0
numpy==1.19.5; python_version >= '3.6'
ocrmypdf==11.4.5
pathvalidate==2.3.2
pdfminer.six==20201018; python_version >= '3.4'
pdftotext==2.1.5
pikepdf==2.5.2
pikepdf==2.2.5
pillow==8.1.0
pluggy==0.13.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
portalocker==2.2.1; python_version >= '3'
psycopg2-binary==2.8.6
pyasn1-modules==0.2.8
pyasn1==0.4.8
pycparser==2.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
pyhamcrest==2.0.2; python_version >= '3.5'
pyopenssl==20.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
python-dateutil==2.8.1
python-dotenv==0.15.0
python-gnupg==0.4.6
python-levenshtein==0.12.2
python-levenshtein==0.12.0
python-magic==0.4.18
pytz==2021.1
pyyaml==5.4.1
pytz==2020.5
redis==3.5.3
regex==2020.11.13
reportlab==3.5.59
requests==2.25.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
scikit-learn==0.24.0
scipy==1.5.4
service-identity==18.1.0
scipy==1.5.4; python_version >= '3.6'
six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
sortedcontainers==2.3.0
sqlparse==0.4.1; python_version >= '3.5'
threadpoolctl==2.1.0; python_version >= '3.5'
tika==1.24
tqdm==4.56.1
twisted[tls]==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
txaio==20.12.1; python_version >= '3.6'
tqdm==4.56.0
typing-extensions==3.7.4.3; python_version < '3.8'
tzlocal==2.1
urllib3==1.26.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
uvicorn[standard]==0.13.3
uvloop==0.14.0
urllib3==1.26.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
watchdog==1.0.2
watchgod==0.6
wcwidth==0.2.5
websockets==8.1
whitenoise==5.2.0
whoosh==2.7.4
zope.interface==5.2.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
zipp==3.4.0; python_version >= '3.6'
+1 -1
View File
@@ -8,7 +8,7 @@ Requires=redis.service
User=paperless
Group=paperless
WorkingDirectory=/opt/paperless/src
ExecStart=/opt/paperless/.local/bin/gunicorn -c /opt/paperless/gunicorn.conf.py paperless.asgi:application
ExecStart=/opt/paperless/.local/bin/gunicorn paperless.wsgi -w 2 -b 0.0.0.0:8000
[Install]
WantedBy=multi-user.target
+64 -308
View File
@@ -2,67 +2,25 @@
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en-US" datatype="plaintext" original="ng2.template">
<body>
<trans-unit id="9103526311244275943" datatype="html">
<source>Document added</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="9204248378636247318" datatype="html">
<source>Document <x id="PH" equiv-text="status.filename"/> was added to paperless.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="1931214133925051574" datatype="html">
<source>Open document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="8582620835547864448" datatype="html">
<source>Could not add <x id="PH" equiv-text="status.filename"/>: <x id="PH_1" equiv-text="status.message"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit id="1710712016675379662" datatype="html">
<source>New document detected</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit id="587031278561344416" datatype="html">
<source>Document <x id="PH" equiv-text="status.filename"/> is being processed by paperless.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit id="4733307402565258070" datatype="html">
<source>Documents</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit id="2155249406916744630" datatype="html">
<source>View &quot;<x id="PH" equiv-text="this.list.savedView.name"/>&quot; saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">109</context>
<context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit id="6837554170707123455" datatype="html">
<source>View &quot;<x id="PH" equiv-text="savedView.name"/>&quot; created successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">130</context>
<context context-type="linenumber">115</context>
</context-group>
</trans-unit>
<trans-unit id="9ca82952a6bc860b5391d5975322d8af8ceddfa4" datatype="html">
@@ -188,35 +146,35 @@
<source>Confirm delete</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">203</context>
<context context-type="linenumber">192</context>
</context-group>
</trans-unit>
<trans-unit id="5382975254277698192" datatype="html">
<source>Do you really want to delete document &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">204</context>
<context context-type="linenumber">193</context>
</context-group>
</trans-unit>
<trans-unit id="6691075929777935948" datatype="html">
<source>The files for this document will be deleted permanently. This operation cannot be undone.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">205</context>
<context context-type="linenumber">194</context>
</context-group>
</trans-unit>
<trans-unit id="719892092227206532" datatype="html">
<source>Delete document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">207</context>
<context context-type="linenumber">196</context>
</context-group>
</trans-unit>
<trans-unit id="1844801255494293730" datatype="html">
<source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">203</context>
</context-group>
</trans-unit>
<trans-unit id="826b25211922a1b46436589233cb6f1a163d89b7" datatype="html">
@@ -524,35 +482,35 @@
<source>Saved view &quot;<x id="PH" equiv-text="savedView.name"/>&quot; deleted.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">67</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit id="5647210819299459618" datatype="html">
<source>Settings saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">87</context>
<context context-type="linenumber">79</context>
</context-group>
</trans-unit>
<trans-unit id="6839066544204061364" datatype="html">
<source>Use system language</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">91</context>
<context context-type="linenumber">83</context>
</context-group>
</trans-unit>
<trans-unit id="7729897675462249787" datatype="html">
<source>Use date format of display language</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">87</context>
</context-group>
</trans-unit>
<trans-unit id="8488620293789898901" datatype="html">
<source>Error while storing settings on server: <x id="PH" equiv-text="JSON.stringify(error.error)"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">111</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit id="121cc5391cd2a5115bc2b3160379ee5b36cd7716" datatype="html">
@@ -569,18 +527,11 @@
<context context-type="linenumber">10</context>
</context-group>
</trans-unit>
<trans-unit id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab" datatype="html">
<source>Notifications</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">115</context>
</context-group>
</trans-unit>
<trans-unit id="99dee94e92dbd9e21a008d4569f9719ed206ae37" datatype="html">
<source>Saved views</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">133</context>
<context context-type="linenumber">114</context>
</context-group>
</trans-unit>
<trans-unit id="bbe41ac2ea4a6c00ea941a41b33105048f8e9f13" datatype="html">
@@ -716,74 +667,32 @@
<context context-type="linenumber">107</context>
</context-group>
</trans-unit>
<trans-unit id="8680abbea249ebe9c2fe35556559c8e1a9eb5841" datatype="html">
<source>Document processing</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">118</context>
</context-group>
</trans-unit>
<trans-unit id="2ad4d76b36341c589d94004ad2a213fd4d6f5ca0" datatype="html">
<source>Show notifications when new documents are detected</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">122</context>
</context-group>
</trans-unit>
<trans-unit id="e775f4f7c40249d31426ae61a21616a0c9d8e84f" datatype="html">
<source>Show notifications when document processing completes successfully</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit id="e3844dd174d8e817ddb551fae28f14ae80ca36b6" datatype="html">
<source>Show notifications when document processing fails</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">124</context>
</context-group>
</trans-unit>
<trans-unit id="af113f7c9f7e13145c3461f61a1aedf12d57bd71" datatype="html">
<source>Suppress notifications on dashboard</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit id="e27bd3804d2936a6897e81c2e52e294490e5e5a8" datatype="html">
<source>This will suppress all messages about document processing status on the dashboard.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit id="8cb90334f5dfd7fc67205085f59381e2a334ccfc" datatype="html">
<source>Appears on</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">145</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit id="6717cf1acf04728fc2b7c39f6d3297f8ff15fde5" datatype="html">
<source>Show on dashboard</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">148</context>
<context context-type="linenumber">129</context>
</context-group>
</trans-unit>
<trans-unit id="541bfc5b123b3f8867fd681eaceefb663a811973" datatype="html">
<source>Show in sidebar</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">152</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit id="abba764a7a595d04dc8c3b26e04b3780d4fdb540" datatype="html">
<source>No saved views defined.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">143</context>
</context-group>
</trans-unit>
<trans-unit id="ef60a738a565f498b858e903e42bc5ffc3cc1299" datatype="html">
@@ -980,42 +889,35 @@
<source>Manage</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">107</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit id="408cb6073e60c5d966296a3207fc596adca75e01" datatype="html">
<source>Admin</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">149</context>
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5" datatype="html">
<source>Info</source>
<trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029" datatype="html">
<source>Misc</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">155</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit id="fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7" datatype="html">
<source>Documentation</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">167</context>
</context-group>
</trans-unit>
<trans-unit id="355a222236bc01b9a8cd3cb9ecf76891125aed69" datatype="html">
<source>GitHub</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">170</context>
</context-group>
</trans-unit>
<trans-unit id="ea3a452c5238897cabc5781308cceb2d37dcf258" datatype="html">
<source>Suggest an idea</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">176</context>
<context context-type="linenumber">174</context>
</context-group>
</trans-unit>
<trans-unit id="af665f8de8fabe306aaf27443957e69bcbbce63c" datatype="html">
@@ -1029,14 +931,14 @@
<source>Open documents</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">87</context>
<context context-type="linenumber">92</context>
</context-group>
</trans-unit>
<trans-unit id="dca5bf9344a759fa5a07f1b21f50286ec242ba44" datatype="html">
<source>Close all</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">101</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit id="5195932016807797291" datatype="html">
@@ -1222,13 +1124,6 @@
<context context-type="linenumber">73</context>
</context-group>
</trans-unit>
<trans-unit id="7894972847287473517" datatype="html">
<source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit id="8639884465898458690" datatype="html">
<source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot; and &quot;<x id="PH_1" equiv-text="items[1].name"/>&quot;</source>
<context-group purpose="location">
@@ -1237,6 +1132,13 @@
</context-group>
<note priority="1" from="description">This is for messages like &apos;modify &quot;tag1&quot; and &quot;tag2&quot;&apos;</note>
</trans-unit>
<trans-unit id="7894972847287473517" datatype="html">
<source>&quot;<x id="PH" equiv-text="i.name"/>&quot;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">116</context>
</context-group>
</trans-unit>
<trans-unit id="760986369763309193" datatype="html">
<source>, </source>
<context-group purpose="location">
@@ -1386,13 +1288,6 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e" datatype="html">
<source>Suggestions:</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="27d158b47717ff9305d19866960418c603f19d55" datatype="html">
<source>Save current view</source>
<context-group purpose="location">
@@ -1428,53 +1323,25 @@
<context context-type="linenumber">4</context>
</context-group>
</trans-unit>
<trans-unit id="6443586946875325554" datatype="html">
<source>Processing: <x id="PH" equiv-text="countUploadingAndProcessing"/></source>
<trans-unit id="8705589528094706681" datatype="html">
<source>The document has been uploaded and will be processed by the consumer shortly.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">32</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit id="9182918211699394982" datatype="html">
<source>Failed: <x id="PH" equiv-text="countFailed"/></source>
<trans-unit id="4956689020592747108" datatype="html">
<source>There was an error while uploading the document: <x id="PH" equiv-text="error.error.document"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">71</context>
</context-group>
</trans-unit>
<trans-unit id="534116346205124059" datatype="html">
<source>Added: <x id="PH" equiv-text="countSuccess"/></source>
<trans-unit id="7554858521017940575" datatype="html">
<source>An error has occurred while uploading the document. Sorry!</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">38</context>
</context-group>
</trans-unit>
<trans-unit id="3852289441366561594" datatype="html">
<source>Connecting...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">118</context>
</context-group>
</trans-unit>
<trans-unit id="1245343823699368872" datatype="html">
<source>Uploading...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit id="7446520539098045935" datatype="html">
<source>Upload complete, waiting...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit id="1405142710727603568" datatype="html">
<source>HTTP error: <x id="PH" equiv-text="error.status"/> <x id="PH_1" equiv-text="error.statusText"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">136</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit id="e022072b3e4dd77e3f09960817ef3359a49963b3" datatype="html">
@@ -1488,39 +1355,23 @@
<source>Drop documents here or</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">13</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>
<trans-unit id="865c511f4a24558ed0e954f9bbbff557bbb8954d" datatype="html">
<source>Browse files</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>
<trans-unit id="33c76d75ce25ce3b05ab22877f1b6b09dcf603ae" datatype="html">
<source>{VAR_PLURAL, plural, =1 {Uploading file...} =other {Uploading <x id="INTERPOLATION"/> files...}}</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="bd4a8607e4a002d939cffb347ec056664dfb2c73" datatype="html">
<source>Dismiss completed</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<note priority="1" from="description">This button dismisses all status messages about processed documents on the dashboard (failed and successful)</note>
</trans-unit>
<trans-unit id="90917e1a0a7bb59e9d11bdde9183e9391963e17b" datatype="html">
<source>{VAR_PLURAL, plural, =1 {One more document} other {<x id="INTERPOLATION"/> more documents}}</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">25</context>
</context-group>
<note priority="1" from="description">This is shown as a summary line when there are more than 5 document in the processing pipeline.</note>
</trans-unit>
<trans-unit id="710254a196a2649674438edf8a15b7ab1f48271b" datatype="html">
<source>Open document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">45</context>
</context-group>
</trans-unit>
<trans-unit id="45854ddec74086b271e62be6a363f4fa5036f7fc" datatype="html">
<source>First steps</source>
<context-group purpose="location">
@@ -1616,186 +1467,91 @@
<source>English (US)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">82</context>
<context context-type="linenumber">74</context>
</context-group>
</trans-unit>
<trans-unit id="1858110241312746425" datatype="html">
<source>German</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">83</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit id="3071065188816255493" datatype="html">
<source>Dutch</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">84</context>
<context context-type="linenumber">76</context>
</context-group>
</trans-unit>
<trans-unit id="7633754075223722162" datatype="html">
<source>French</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">85</context>
</context-group>
</trans-unit>
<trans-unit id="2119857572761283468" datatype="html">
<source>Document already exists.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">15</context>
</context-group>
</trans-unit>
<trans-unit id="148389968432135849" datatype="html">
<source>File not found.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="1520671543092565667" datatype="html">
<source>Pre-consume script does not exist.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">17</context>
</context-group>
<note priority="1" from="description">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit id="7742915911032564889" datatype="html">
<source>Error while executing pre-consume script.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">18</context>
</context-group>
<note priority="1" from="description">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit id="8995193730018060346" datatype="html">
<source>Post-consume script does not exist.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">19</context>
</context-group>
<note priority="1" from="description">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit id="256773668518189604" datatype="html">
<source>Error while executing post-consume script.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">20</context>
</context-group>
<note priority="1" from="description">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit id="6252258095055634191" datatype="html">
<source>Received new file.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="7337565919209746135" datatype="html">
<source>File type not supported.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit id="5002399167376099234" datatype="html">
<source>Processing document...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="1085975194762600381" datatype="html">
<source>Generating thumbnail...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit id="3280851677698431426" datatype="html">
<source>Retrieving date from document...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="7162102384876037296" datatype="html">
<source>Saving document...</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="4550450765009165976" datatype="html">
<source>Finished.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">27</context>
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit id="1519954996184640001" datatype="html">
<source>Error</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit id="5037437391296624618" datatype="html">
<source>Information</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">39</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit id="7517688192215738656" datatype="html">
<source>ASN</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">17</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="2691296884221415710" datatype="html">
<source>Correspondent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">18</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit id="5701618810648052610" datatype="html">
<source>Title</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">19</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="5066119607229701477" datatype="html">
<source>Document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">20</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="4207916966377787111" datatype="html">
<source>Created</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">21</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="231679111972850796" datatype="html">
<source>Added</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">22</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="3553216189604488439" datatype="html">
<source>Modified</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">23</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit id="2056433880533904076" datatype="html">
+4 -57
View File
@@ -1,70 +1,17 @@
import { SettingsService, SETTINGS_KEYS } from './services/settings.service';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { ConsumerStatusService } from './services/consumer-status.service';
import { ToastService } from './services/toast.service';
import { Component } from '@angular/core';
import { SettingsService } from './services/settings.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy {
export class AppComponent {
newDocumentSubscription: Subscription;
successSubscription: Subscription;
failedSubscription: Subscription;
constructor (private settings: SettingsService, private consumerStatusService: ConsumerStatusService, private toastService: ToastService, private router: Router) {
constructor (private settings: SettingsService) {
let anyWindow = (window as any)
anyWindow.pdfWorkerSrc = '/assets/js/pdf.worker.min.js';
this.settings.updateDarkModeSettings()
}
ngOnDestroy(): void {
this.consumerStatusService.disconnect()
if (this.successSubscription) {
this.successSubscription.unsubscribe()
}
if (this.failedSubscription) {
this.failedSubscription.unsubscribe()
}
if (this.newDocumentSubscription) {
this.newDocumentSubscription.unsubscribe()
}
}
private showNotification(key) {
if (this.router.url == '/dashboard' && this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD)) {
return false
}
return this.settings.get(key)
}
ngOnInit(): void {
this.consumerStatusService.connect()
this.successSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => {
if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)) {
this.toastService.show({title: $localize`Document added`, delay: 10000, content: $localize`Document ${status.filename} was added to paperless.`, actionName: $localize`Open document`, action: () => {
this.router.navigate(['documents', status.documentId])
}})
}
})
this.failedSubscription = this.consumerStatusService.onDocumentConsumptionFailed().subscribe(status => {
if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)) {
this.toastService.showError($localize`Could not add ${status.filename}\: ${status.message}`)
}
})
this.newDocumentSubscription = this.consumerStatusService.onDocumentDetected().subscribe(status => {
if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT)) {
this.toastService.show({title: $localize`New document detected`, delay: 5000, content: $localize`Document ${status.filename} is being processed by paperless.`})
}
})
}
}
@@ -52,7 +52,12 @@
<div class="container-fluid">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" [ngbCollapse]="isMenuCollapsed">
<div class="sidebar-sticky pt-3 d-flex flex-column justify-space-around">
<div style="position: absolute; bottom: 0; left: 0;" class="text-muted p-1">
{{versionString}}
</div>
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" routerLink="dashboard" routerLinkActive="active" (click)="closeMenu()">
@@ -70,7 +75,7 @@
</li>
</ul>
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='savedViewService.sidebarViews.length > 0'>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted" *ngIf='savedViewService.sidebarViews.length > 0'>
<ng-container i18n>Saved views</ng-container>
</h6>
<ul class="nav flex-column mb-2">
@@ -83,7 +88,7 @@
</li>
</ul>
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
<ng-container i18n>Open documents</ng-container>
</h6>
<ul class="nav flex-column mb-2">
@@ -103,7 +108,7 @@
</li>
</ul>
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted">
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<ng-container i18n>Manage</ng-container>
</h6>
<ul class="nav flex-column mb-2">
@@ -151,8 +156,8 @@
</li>
</ul>
<h6 class="sidebar-heading px-3 mt-auto pt-4 mb-1 text-muted">
<ng-container i18n>Info</ng-container>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<ng-container i18n>Misc</ng-container>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
@@ -163,24 +168,11 @@
</a>
</li>
<li class="nav-item">
<div class="d-flex w-100 flex-wrap">
<a class="nav-link pr-0 pb-0" target="_blank" rel="noopener noreferrer" href="https://github.com/jonaswinkler/paperless-ng">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="sidebaricon bi bi-github" viewBox="0 0 16 16">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
</svg>&nbsp;<ng-container i18n>GitHub</ng-container>
</a>
<a class="nav-link-additional small text-muted ml-3" target="_blank" rel="noopener noreferrer" href="https://github.com/jonaswinkler/paperless-ng/discussions/categories/feature-requests" title="Suggest an idea">
<svg xmlns="http://www.w3.org/2000/svg" width=".9rem" height=".9rem" fill="currentColor" class="bi bi-lightbulb pr-1" viewBox="0 0 16 16">
<path d="M2 6a6 6 0 1 1 10.174 4.31c-.203.196-.359.4-.453.619l-.762 1.769A.5.5 0 0 1 10.5 13a.5.5 0 0 1 0 1 .5.5 0 0 1 0 1l-.224.447a1 1 0 0 1-.894.553H6.618a1 1 0 0 1-.894-.553L5.5 15a.5.5 0 0 1 0-1 .5.5 0 0 1 0-1 .5.5 0 0 1-.46-.302l-.761-1.77a1.964 1.964 0 0 0-.453-.618A5.984 5.984 0 0 1 2 6zm6-5a5 5 0 0 0-3.479 8.592c.263.254.514.564.676.941L5.83 12h4.342l.632-1.467c.162-.377.413-.687.676-.941A5 5 0 0 0 8 1z"/>
</svg>
<ng-container i18n>Suggest an idea</ng-container>
</a>
</div>
</li>
<li class="nav-item mt-2">
<div class="px-3 py-2 text-muted small">
{{versionString}}
</div>
<a class="nav-link" target="_blank" rel="noopener noreferrer" href="https://github.com/jonaswinkler/paperless-ng">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#link"/>
</svg>&nbsp;<ng-container i18n>GitHub</ng-container>
</a>
</li>
</ul>
</div>
@@ -24,7 +24,6 @@
padding-top: 0.5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
min-height: min-content;
}
@supports ((position: -webkit-sticky) or (position: sticky)) {
.sidebar-sticky {
@@ -58,20 +57,6 @@
text-transform: uppercase;
}
.nav {
flex-wrap: nowrap;
}
.nav-item .nav-link-additional {
margin-top: 0.2rem;
margin-left: 0.25rem;
padding-top: 0.5rem;
svg {
margin-bottom: 2px;
}
}
/*
* Navbar
*/
@@ -22,12 +22,4 @@
</div>
</div>
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
<small *ngIf="getSuggestions().length > 0">
<span i18n>Suggestions:</span>&nbsp;
<ng-container *ngFor="let s of getSuggestions()">
<a (click)="value = s.id; onChange(value)" [routerLink]="">{{s.name}}</a>&nbsp;
</ng-container>
</small>
</div>
@@ -30,22 +30,11 @@ export class SelectComponent extends AbstractInputComponent<number> {
@Input()
allowNull: boolean = false
@Input()
suggestions: number[]
@Output()
createNew = new EventEmitter()
showPlusButton(): boolean {
return this.createNew.observers.length > 0
}
getSuggestions() {
if (this.suggestions && this.items) {
return this.suggestions.filter(id => id != this.value).map(id => this.items.find(item => item.id == id))
} else {
return []
}
}
}
@@ -2,25 +2,30 @@
<label for="tags" i18n>Tags</label>
<div class="input-group flex-nowrap">
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="value"
<ng-select name="tags" [items]="tags" bindLabel="name" bindValue="id" [(ngModel)]="displayValue"
[multiple]="true"
[closeOnSelect]="false"
[clearSearchOnAdd]="true"
[disabled]="disabled"
[hideSelected]="true"
(change)="onChange(value)"
(blur)="onTouched()">
(change)="ngSelectChange()">
<ng-template ng-label-tmp let-item="item">
<span class="tag-wrap tag-wrap-delete" (click)="removeTag(item.id)">
<svg width="1.2em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<use xlink:href="assets/bootstrap-icons.svg#x"/>
</svg>
<app-tag *ngIf="item.id && tags" style="background-color: none;" [tag]="getTag(item.id)"></app-tag>
<app-tag style="background-color: none;" [tag]="getTag(item.id)"></app-tag>
</span>
</ng-template>
<ng-template ng-option-tmp let-item="item" let-index="index" let-search="searchTerm">
<div class="tag-wrap">
<app-tag *ngIf="item.id && tags" class="mr-2" [tag]="getTag(item.id)"></app-tag>
<div class="selected-icon d-inline-block mr-1">
<svg *ngIf="displayValue.includes(item.id)" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<use xlink:href="assets/bootstrap-icons.svg#check"/>
</svg>
</div>
<app-tag class="mr-2" [tag]="getTag(item.id)"></app-tag>
</div>
</ng-template>
</ng-select>
@@ -34,13 +39,5 @@
</div>
</div>
<small class="form-text text-muted" *ngIf="hint">{{hint}}</small>
<small *ngIf="getSuggestions().length > 0">
<span i18n>Suggestions:</span>&nbsp;
<ng-container *ngFor="let tag of getSuggestions()">
<a (click)="addTag(tag.id)" [routerLink]="">{{tag.name}}</a>&nbsp;
</ng-container>
</small>
</div>
@@ -26,6 +26,9 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
writeValue(newValue: number[]): void {
this.value = newValue
if (this.tags) {
this.displayValue = newValue
}
}
registerOnChange(fn: any): void {
this.onChange = fn;
@@ -40,6 +43,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
ngOnInit(): void {
this.tagService.listAll().subscribe(result => {
this.tags = result.results
this.displayValue = this.value
})
}
@@ -49,28 +53,23 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
@Input()
hint
@Input()
suggestions: number[]
value: number[]
displayValue: number[] = []
tags: PaperlessTag[]
getTag(id) {
if (this.tags) {
return this.tags.find(tag => tag.id == id)
} else {
return null
}
return this.tags.find(tag => tag.id == id)
}
removeTag(id) {
let index = this.value.indexOf(id)
let index = this.displayValue.indexOf(id)
if (index > -1) {
let oldValue = this.value
let oldValue = this.displayValue
oldValue.splice(index, 1)
this.value = [...oldValue]
this.onChange(this.value)
this.displayValue = [...oldValue]
this.onChange(this.displayValue)
}
}
@@ -80,23 +79,15 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
modal.componentInstance.success.subscribe(newTag => {
this.tagService.listAll().subscribe(tags => {
this.tags = tags.results
this.value = [...this.value, newTag.id]
this.onChange(this.value)
this.displayValue = [...this.displayValue, newTag.id]
this.onChange(this.displayValue)
})
})
}
getSuggestions() {
if (this.suggestions && this.tags) {
return this.suggestions.filter(id => !this.value.includes(id)).map(id => this.tags.find(tag => tag.id == id))
} else {
return []
}
}
addTag(id) {
this.value = [...this.value, id]
this.onChange(this.value)
ngSelectChange() {
this.value = this.displayValue
this.onChange(this.displayValue)
}
}
@@ -3,6 +3,5 @@
[header]="toast.title" [autohide]="true" [delay]="toast.delay"
[class]="toast.classname"
(hide)="toastService.closeToast(toast)">
<p>{{toast.content}}</p>
<p *ngIf="toast.action"><button class="btn btn-sm btn-outline-secondary" (click)="toastService.closeToast(toast); toast.action()">{{toast.actionName}}</button></p>
</ngb-toast>
{{toast.content}}
</ngb-toast>
@@ -19,6 +19,6 @@
<app-statistics-widget></app-statistics-widget>
<app-upload-file-widget></app-upload-file-widget>
</div>
</div>
@@ -18,4 +18,4 @@
</tbody>
</table>
</app-widget-frame>
</app-widget-frame>
@@ -1,10 +1,8 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { ConsumerStatusService } from 'src/app/services/consumer-status.service';
import { DocumentService } from 'src/app/services/rest/document.service';
@Component({
@@ -12,33 +10,19 @@ import { DocumentService } from 'src/app/services/rest/document.service';
templateUrl: './saved-view-widget.component.html',
styleUrls: ['./saved-view-widget.component.scss']
})
export class SavedViewWidgetComponent implements OnInit, OnDestroy {
export class SavedViewWidgetComponent implements OnInit {
constructor(
private documentService: DocumentService,
private router: Router,
private list: DocumentListViewService,
private consumerStatusService: ConsumerStatusService) { }
private list: DocumentListViewService) { }
@Input()
savedView: PaperlessSavedView
documents: PaperlessDocument[] = []
subscription: Subscription
ngOnInit(): void {
this.reload()
this.subscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => {
this.reload()
})
}
ngOnDestroy(): void {
this.subscription.unsubscribe()
}
reload() {
this.documentService.listFiltered(1,10,this.savedView.sort_field, this.savedView.sort_reverse, this.savedView.filter_rules).subscribe(result => {
this.documents = result.results
})
@@ -3,4 +3,4 @@
<p class="card-text" i18n>Documents in inbox: {{statistics.documents_inbox}}</p>
<p class="card-text" i18n>Total documents: {{statistics.documents_total}}</p>
</ng-container>
</app-widget-frame>
</app-widget-frame>
@@ -1,7 +1,6 @@
import { HttpClient } from '@angular/common/http';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { ConsumerStatusService } from 'src/app/services/consumer-status.service';
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
export interface Statistics {
@@ -15,34 +14,20 @@ export interface Statistics {
templateUrl: './statistics-widget.component.html',
styleUrls: ['./statistics-widget.component.scss']
})
export class StatisticsWidgetComponent implements OnInit, OnDestroy {
export class StatisticsWidgetComponent implements OnInit {
constructor(private http: HttpClient,
private consumerStatusService: ConsumerStatusService) { }
constructor(private http: HttpClient) { }
statistics: Statistics = {}
subscription: Subscription
private getStatistics(): Observable<Statistics> {
getStatistics(): Observable<Statistics> {
return this.http.get(`${environment.apiBaseUrl}statistics/`)
}
reload() {
ngOnInit(): void {
this.getStatistics().subscribe(statistics => {
this.statistics = statistics
})
}
ngOnInit(): void {
this.reload()
this.subscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => {
this.reload()
})
}
ngOnDestroy(): void {
this.subscription.unsubscribe()
}
}
@@ -1,52 +1,18 @@
<app-widget-frame title="Upload new documents" i18n-title>
<div header-buttons>
<a *ngIf="getStatusSuccess().length > 0" (click)="dismissCompleted()" [routerLink]="" >
<span i18n="This button dismisses all status messages about processed documents on the dashboard (failed and successful)">Dismiss completed</span>&nbsp;
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-check2-all" viewBox="0 0 16 16">
<path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7zm-4.208 7l-.896-.897.707-.707.543.543 6.646-6.647a.5.5 0 0 1 .708.708l-7 7a.5.5 0 0 1-.708 0z"/>
<path d="M5.354 7.146l.896.897-.707.707-.897-.896a.5.5 0 1 1 .708-.708z"/>
</svg>
</a>
</div>
<div content>
<form>
<ngx-file-drop dropZoneLabel="Drop documents here or" browseBtnLabel="Browse files" (onFileDrop)="dropped($event)"
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)" dropZoneClassName="bg-light card"
multiple="true" contentClassName="justify-content-center d-flex align-items-center py-5 px-2" [showBrowseBtn]=true
multiple="true" contentClassName="justify-content-center d-flex align-items-center p-5" [showBrowseBtn]=true
browseBtnClassName="btn btn-sm btn-outline-primary ml-2" i18n-dropZoneLabel i18n-browseBtnLabel>
</ngx-file-drop>
</form>
<p class="mt-3" *ngIf="getStatus().length > 0">{{getStatusSummary()}}</p>
<div *ngFor="let status of getStatus()">
<ng-container [ngTemplateOutlet]="consumerAlert" [ngTemplateOutletContext]="{ $implicit: status }"></ng-container>
</div>
<div *ngIf="getStatusHidden().length" class="alerts-hidden">
<p *ngIf="!alertsExpanded" class="mt-3 mb-0 text-center">
<span i18n="This is shown as a summary line when there are more than 5 document in the processing pipeline.">{getStatusHidden().length, plural, =1 {One more document} other {{{getStatusHidden().length}} more documents}}</span>
&nbsp;&bull;&nbsp;
<a [routerLink]="" (click)="alertsExpanded = !alertsExpanded" aria-controls="hiddenAlerts" [attr.aria-expanded]="alertsExpanded" i18n>Show all</a>
</p>
<div #hiddenAlerts="ngbCollapse" [(ngbCollapse)]="!alertsExpanded">
<div *ngFor="let status of getStatusHidden()">
<ng-container [ngTemplateOutlet]="consumerAlert" [ngTemplateOutletContext]="{ $implicit: status }"></ng-container>
</div>
</div>
<div *ngIf="uploadVisible" class="mt-3">
<p i18n>{uploadStatus.length, plural, =1 {Uploading file...} =other {Uploading {{uploadStatus.length}} files...}}</p>
<ngb-progressbar [value]="loadedSum" [max]="totalSum" [striped]="true" [animated]="uploadStatus.length > 0">
</ngb-progressbar>
</div>
</div>
</app-widget-frame>
<ng-template #consumerAlert let-status>
<ngb-alert type="secondary" class="mt-2 mb-0" [dismissible]="isFinished(status)" (closed)="dismiss(status)">
<h6 class="alert-heading">{{status.filename}}</h6>
<p class="mb-0 pb-1" *ngIf="!isFinished(status) || (isFinished(status) && !status.documentId)">{{status.message}}</p>
<ngb-progressbar [value]="status.getProgress()" [max]="1" [type]="getStatusColor(status)"></ngb-progressbar>
<div *ngIf="isFinished(status)">
<button *ngIf="status.documentId" class="btn btn-sm btn-outline-primary btn-open" routerLink="/documents/{{status.documentId}}" (click)="dismiss(status)">
<small i18n>Open document</small>
<svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
</svg>
</button>
</div>
</ngb-alert>
</ng-template>
</app-widget-frame>
@@ -1,35 +0,0 @@
@import "/src/theme";
form {
position: relative;
}
.alert-heading {
font-size: 80%;
font-weight: bold;
}
.alerts-hidden {
.btn {
line-height: 1;
}
}
.btn-open {
line-height: 1;
svg {
margin-top: -1px;
}
}
::ng-deep .progress {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
height: auto;
mix-blend-mode: soft-light;
pointer-events: none;
}
@@ -1,10 +1,14 @@
import { HttpEventType } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop';
import { ConsumerStatusService, FileStatus, FileStatusPhase } from 'src/app/services/consumer-status.service';
import { DocumentService } from 'src/app/services/rest/document.service';
import { ToastService } from 'src/app/services/toast.service';
const MAX_ALERTS = 5
interface UploadStatus {
loaded: number
total: number
}
@Component({
selector: 'app-upload-file-widget',
@@ -12,89 +16,8 @@ const MAX_ALERTS = 5
styleUrls: ['./upload-file-widget.component.scss']
})
export class UploadFileWidgetComponent implements OnInit {
alertsExpanded = false
constructor(
private documentService: DocumentService,
private consumerStatusService: ConsumerStatusService
) { }
getStatus() {
return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS)
}
getStatusSummary() {
let strings = []
let countUploadingAndProcessing = this.consumerStatusService.getConsumerStatusNotCompleted().length
let countFailed = this.getStatusFailed().length
let countSuccess = this.getStatusSuccess().length
if (countUploadingAndProcessing > 0) {
strings.push($localize`Processing: ${countUploadingAndProcessing}`)
}
if (countFailed > 0) {
strings.push($localize`Failed: ${countFailed}`)
}
if (countSuccess > 0) {
strings.push($localize`Added: ${countSuccess}`)
}
return strings.join($localize`:this string is used to separate processing, failed and added on the file upload widget:, `)
}
getStatusHidden() {
if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS) return []
else return this.consumerStatusService.getConsumerStatus().slice(MAX_ALERTS)
}
getStatusUploading() {
return this.consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
}
getStatusFailed() {
return this.consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
}
getStatusSuccess() {
return this.consumerStatusService.getConsumerStatus(FileStatusPhase.SUCCESS)
}
getStatusCompleted() {
return this.consumerStatusService.getConsumerStatusCompleted()
}
getTotalUploadProgress() {
let current = 0
let max = 0
this.getStatusUploading().forEach(status => {
current += status.currentPhaseProgress
max += status.currentPhaseMaxProgress
})
return current / Math.max(max, 1)
}
isFinished(status: FileStatus) {
return status.phase == FileStatusPhase.FAILED || status.phase == FileStatusPhase.SUCCESS
}
getStatusColor(status: FileStatus) {
switch (status.phase) {
case FileStatusPhase.PROCESSING:
case FileStatusPhase.UPLOADING:
return "primary"
case FileStatusPhase.FAILED:
return "danger"
case FileStatusPhase.SUCCESS:
return "success"
}
}
dismiss(status: FileStatus) {
this.consumerStatusService.dismiss(status)
}
dismissCompleted() {
this.consumerStatusService.dismissCompleted()
}
constructor(private documentService: DocumentService, private toastService: ToastService) { }
ngOnInit(): void {
}
@@ -105,39 +28,54 @@ export class UploadFileWidgetComponent implements OnInit {
public fileLeave(event){
}
uploadStatus: UploadStatus[] = []
completedFiles = 0
uploadVisible = false
get loadedSum() {
return this.uploadStatus.map(s => s.loaded).reduce((a,b) => a+b, this.completedFiles > 0 ? 1 : 0)
}
get totalSum() {
return this.uploadStatus.map(s => s.total).reduce((a,b) => a+b, 1)
}
public dropped(files: NgxFileDropEntry[]) {
for (const droppedFile of files) {
if (droppedFile.fileEntry.isFile) {
let uploadStatusObject: UploadStatus = {loaded: 0, total: 1}
this.uploadStatus.push(uploadStatusObject)
this.uploadVisible = true
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
fileEntry.file((file: File) => {
let formData = new FormData()
formData.append('document', file, file.name)
let status = this.consumerStatusService.newFileUpload(file.name)
status.message = $localize`Connecting...`
this.documentService.uploadDocument(formData).subscribe(event => {
if (event.type == HttpEventType.UploadProgress) {
status.updateProgress(FileStatusPhase.UPLOADING, event.loaded, event.total)
status.message = $localize`Uploading...`
uploadStatusObject.loaded = event.loaded
uploadStatusObject.total = event.total
} else if (event.type == HttpEventType.Response) {
status.taskId = event.body["task_id"]
status.message = $localize`Upload complete, waiting...`
this.uploadStatus.splice(this.uploadStatus.indexOf(uploadStatusObject), 1)
this.completedFiles += 1
this.toastService.showInfo($localize`The document has been uploaded and will be processed by the consumer shortly.`)
}
}, error => {
this.uploadStatus.splice(this.uploadStatus.indexOf(uploadStatusObject), 1)
this.completedFiles += 1
switch (error.status) {
case 400: {
this.consumerStatusService.fail(status, error.error.document)
this.toastService.showInfo($localize`There was an error while uploading the document: ${error.error.document}`)
break;
}
default: {
this.consumerStatusService.fail(status, $localize`HTTP error: ${error.status} ${error.statusText}`)
this.toastService.showInfo($localize`An error has occurred while uploading the document. Sorry!`)
break;
}
}
})
});
}
@@ -60,10 +60,10 @@
<app-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" formControlName='archive_serial_number'></app-input-number>
<app-input-date-time i18n-titleDate titleDate="Date created" formControlName="created"></app-input-date-time>
<app-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true"
(createNew)="createCorrespondent()" [suggestions]="suggestions?.correspondents"></app-input-select>
(createNew)="createCorrespondent()"></app-input-select>
<app-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true"
(createNew)="createDocumentType()" [suggestions]="suggestions?.document_types"></app-input-select>
<app-input-tags formControlName="tags" [suggestions]="suggestions?.tags"></app-input-tags>
(createNew)="createDocumentType()"></app-input-select>
<app-input-tags formControlName="tags"></app-input-tags>
</ng-template>
</li>
@@ -145,6 +145,6 @@
<ng-container *ngIf="getContentType() == 'text/plain'">
<object [data]="previewUrl | safe" type="text/plain" class="preview-sticky" width="100%"></object>
</ng-container>
</div>
</div>
@@ -19,7 +19,6 @@ import { PDFDocumentProxy } from 'ng2-pdf-viewer';
import { ToastService } from 'src/app/services/toast.service';
import { TextComponent } from '../common/input/text/text.component';
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions';
@Component({
selector: 'app-document-detail',
@@ -41,8 +40,6 @@ export class DocumentDetailComponent implements OnInit {
documentId: number
document: PaperlessDocument
metadata: PaperlessDocumentMetadata
suggestions: PaperlessDocumentSuggestions
title: string
previewUrl: string
downloadUrl: string
@@ -98,7 +95,6 @@ export class DocumentDetailComponent implements OnInit {
this.previewUrl = this.documentsService.getPreviewUrl(this.documentId)
this.downloadUrl = this.documentsService.getDownloadUrl(this.documentId)
this.downloadOriginalUrl = this.documentsService.getDownloadUrl(this.documentId, true)
this.suggestions = null
if (this.openDocumentService.getOpenDocument(this.documentId)) {
this.updateComponent(this.openDocumentService.getOpenDocument(this.documentId))
} else {
@@ -115,13 +111,6 @@ export class DocumentDetailComponent implements OnInit {
this.document = doc
this.documentsService.getMetadata(doc.id).subscribe(result => {
this.metadata = result
}, error => {
this.metadata = null
})
this.documentsService.getSuggestions(doc.id).subscribe(result => {
this.suggestions = result
}, error => {
this.suggestions = null
})
this.title = this.documentTitlePipe.transform(doc.title)
this.documentForm.patchValue(doc)
@@ -109,7 +109,7 @@ export class BulkEditorComponent {
if (items.length == 0) {
return ""
} else if (items.length == 1) {
return $localize`"${items[0].name}"`
return items[0].name
} else if (items.length == 2) {
return $localize`:This is for messages like 'modify "tag1" and "tag2"':"${items[0].name}" and "${items[1].name}"`
} else {
@@ -44,9 +44,9 @@
</svg>&nbsp;<span class="d-block d-md-inline" i18n>Edit</span>
</a>
<a class="btn btn-sm btn-outline-secondary" [href]="getPreviewUrl()">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-search" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M10.442 10.442a1 1 0 0 1 1.415 0l3.85 3.85a1 1 0 0 1-1.414 1.415l-3.85-3.85a1 1 0 0 1 0-1.415z"/>
<path fill-rule="evenodd" d="M6.5 12a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zM13 6.5a6.5 6.5 0 1 1-13 0 6.5 6.5 0 0 1 13 0z"/>
</svg>&nbsp;<span class="d-block d-md-inline" i18n>View</span>
</a>
<a class="btn btn-sm btn-outline-secondary" [href]="getDownloadUrl()">
@@ -38,9 +38,9 @@
</svg>
</a>
<a [href]="getPreviewUrl()" class="btn btn-sm btn-outline-secondary" title="View in browser" i18n-title>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-search" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M10.442 10.442a1 1 0 0 1 1.415 0l3.85 3.85a1 1 0 0 1-1.414 1.415l-3.85-3.85a1 1 0 0 1 0-1.415z"/>
<path fill-rule="evenodd" d="M6.5 12a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zM13 6.5a6.5 6.5 0 1 1-13 0 6.5 6.5 0 0 1 13 0z"/>
</svg>
</a>
<a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" (click)="$event.stopPropagation()" i18n-title>
@@ -1,11 +1,9 @@
import { AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { AfterViewInit, Component, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Subscription } from 'rxjs';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive';
import { ConsumerStatusService } from 'src/app/services/consumer-status.service';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service';
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
@@ -18,7 +16,7 @@ import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-vi
templateUrl: './document-list.component.html',
styleUrls: ['./document-list.component.scss']
})
export class DocumentListComponent implements OnInit, OnDestroy {
export class DocumentListComponent implements OnInit {
constructor(
public list: DocumentListViewService,
@@ -26,9 +24,7 @@ export class DocumentListComponent implements OnInit, OnDestroy {
public route: ActivatedRoute,
private router: Router,
private toastService: ToastService,
private modalService: NgbModal,
private consumerStatusService: ConsumerStatusService
) { }
private modalService: NgbModal) { }
@ViewChild("filterEditor")
private filterEditor: FilterEditorComponent
@@ -39,8 +35,6 @@ export class DocumentListComponent implements OnInit, OnDestroy {
filterRulesModified: boolean = false
private consumptionFinishedSubscription: Subscription
get isFiltered() {
return this.list.filterRules?.length > 0
}
@@ -69,9 +63,6 @@ export class DocumentListComponent implements OnInit, OnDestroy {
if (localStorage.getItem('document-list:displayMode') != null) {
this.displayMode = localStorage.getItem('document-list:displayMode')
}
this.consumptionFinishedSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(() => {
this.list.reload()
})
this.route.paramMap.subscribe(params => {
this.list.clear()
if (params.has('id')) {
@@ -92,12 +83,6 @@ export class DocumentListComponent implements OnInit, OnDestroy {
})
}
ngOnDestroy() {
if (this.consumptionFinishedSubscription) {
this.consumptionFinishedSubscription.unsubscribe()
}
}
loadViewConfig(view: PaperlessSavedView) {
this.list.load(view)
this.list.reload()
@@ -1,18 +1,27 @@
<app-page-header title="Logs" i18n-title>
<div ngbDropdown class="btn-group">
<button class="btn btn-outline-primary btn-sm" id="dropdownBasic1" ngbDropdownToggle>
<svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#funnel" />
</svg>&nbsp;<ng-container i18n>Filter</ng-container>
</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<button *ngFor="let f of getLevels()" ngbDropdownItem (click)="setLevel(f.id)"
[class.active]="level == f.id">{{f.name}}</button>
</div>
</div>
</app-page-header>
<ul ngbNav #nav="ngbNav" [(activeId)]="activeLog" (activeIdChange)="reloadLogs()" class="nav-tabs">
<li *ngFor="let logFile of logFiles" [ngbNavItem]="logFile">
<a ngbNavLink>{{logFile}}.log</a>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<div class="bg-dark p-3 mb-3 text-light text-monospace log-container">
<div class="bg-dark p-3 mb-3 text-light text-monospace" infiniteScroll (scrolled)="onScroll()">
<p
class="m-0 p-0 log-entry-{{getLogLevel(log)}}"
*ngFor="let log of logs" style="white-space: pre;">{{log}}</p>
class="m-0 p-0 log-entry-{{log.level}}"
*ngFor="let log of logs">
{{log.created | customDate:'short'}}
{{getLevelText(log.level)}}
{{log.message}}
</p>
</div>
@@ -13,12 +13,4 @@
.log-entry-50 {
color: lightcoral !important;
font-weight: bold;
}
.log-container {
overflow: scroll;
height: calc(100vh - 190px);
top: 70px;
}
@@ -1,4 +1,5 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { LOG_LEVELS, LOG_LEVEL_INFO, PaperlessLog } from 'src/app/data/paperless-log';
import { LogService } from 'src/app/services/rest/log.service';
@Component({
@@ -10,42 +11,38 @@ export class LogsComponent implements OnInit {
constructor(private logService: LogService) { }
logs: string[] = []
logFiles: string[] = []
activeLog: string
logs: PaperlessLog[] = []
level: number = LOG_LEVEL_INFO
ngOnInit(): void {
this.logService.list().subscribe(result => {
this.logFiles = result
if (this.logFiles.length > 0) {
this.activeLog = this.logFiles[0]
this.reloadLogs()
}
})
this.reload()
}
reloadLogs() {
this.logService.get(this.activeLog).subscribe(result => {
this.logs = result
}, error => {
this.logs = []
})
reload() {
this.logService.list(1, 50, 'created', true, {'level__gte': this.level}).subscribe(result => this.logs = result.results)
}
getLogLevel(log: string) {
if (log.indexOf("[DEBUG]") != -1) {
return 10
} else if (log.indexOf("[WARNING]") != -1) {
return 30
} else if (log.indexOf("[ERROR]") != -1) {
return 40
} else if (log.indexOf("[CRITICAL]") != -1) {
return 50
} else {
return 20
getLevelText(level: number) {
return LOG_LEVELS.find(l => l.id == level)?.name
}
onScroll() {
let lastCreated = null
if (this.logs.length > 0) {
lastCreated = new Date(this.logs[this.logs.length-1].created).toISOString()
}
this.logService.list(1, 25, 'created', true, {'created__lt': lastCreated, 'level__gte': this.level}).subscribe(result => {
this.logs.push(...result.results)
})
}
getLevels() {
return LOG_LEVELS
}
setLevel(id) {
this.level = id
this.reload()
}
}
@@ -110,26 +110,7 @@
</ng-template>
</li>
<li [ngbNavItem]="2">
<a ngbNavLink i18n>Notifications</a>
<ng-template ngbNavContent>
<h4 i18n>Document processing</h4>
<div class="form-row form-group">
<div class="offset-md-3 col">
<app-input-check i18n-title title="Show notifications when new documents are detected" formControlName="notificationsConsumerNewDocument"></app-input-check>
<app-input-check i18n-title title="Show notifications when document processing completes successfully" formControlName="notificationsConsumerSuccess"></app-input-check>
<app-input-check i18n-title title="Show notifications when document processing fails" formControlName="notificationsConsumerFailed"></app-input-check>
<app-input-check i18n-title title="Suppress notifications on dashboard" formControlName="notificationsConsumerSuppressOnDashboard" i18n-hint hint="This will suppress all messages about document processing status on the dashboard."></app-input-check>
</div>
</div>
</ng-template>
</li>
<li [ngbNavItem]="3">
<a ngbNavLink i18n>Saved views</a>
<ng-template ngbNavContent>
@@ -26,10 +26,6 @@ export class SettingsComponent implements OnInit {
'displayLanguage': new FormControl(this.settings.getLanguage()),
'dateLocale': new FormControl(this.settings.get(SETTINGS_KEYS.DATE_LOCALE)),
'dateFormat': new FormControl(this.settings.get(SETTINGS_KEYS.DATE_FORMAT)),
'notificationsConsumerNewDocument': new FormControl(this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT)),
'notificationsConsumerSuccess': new FormControl(this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)),
'notificationsConsumerFailed': new FormControl(this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)),
'notificationsConsumerSuppressOnDashboard': new FormControl(this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD)),
})
savedViews: PaperlessSavedView[]
@@ -77,10 +73,6 @@ export class SettingsComponent implements OnInit {
this.settings.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, this.settingsForm.value.useNativePdfViewer)
this.settings.set(SETTINGS_KEYS.DATE_LOCALE, this.settingsForm.value.dateLocale)
this.settings.set(SETTINGS_KEYS.DATE_FORMAT, this.settingsForm.value.dateFormat)
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT, this.settingsForm.value.notificationsConsumerNewDocument)
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS, this.settingsForm.value.notificationsConsumerSuccess)
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, this.settingsForm.value.notificationsConsumerFailed)
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, this.settingsForm.value.notificationsConsumerSuppressOnDashboard)
this.settings.setLanguage(this.settingsForm.value.displayLanguage)
this.documentListViewService.updatePageSize()
this.settings.updateDarkModeSettings()
@@ -1,9 +0,0 @@
export interface PaperlessDocumentSuggestions {
tags?: number[]
correspondents?: number[]
document_types?: number[]
}
+27
View File
@@ -0,0 +1,27 @@
export const LOG_LEVEL_DEBUG = 10
export const LOG_LEVEL_INFO = 20
export const LOG_LEVEL_WARNING = 30
export const LOG_LEVEL_ERROR = 40
export const LOG_LEVEL_CRITICAL = 50
export const LOG_LEVELS = [
{id: LOG_LEVEL_DEBUG, name: "DEBUG"},
{id: LOG_LEVEL_INFO, name: "INFO"},
{id: LOG_LEVEL_WARNING, name: "WARNING"},
{id: LOG_LEVEL_ERROR, name: "ERROR"},
{id: LOG_LEVEL_CRITICAL, name: "CRITICAL"}
]
export interface PaperlessLog {
id?: number
group?: string
message?: string
created?: Date
level?: number
}
@@ -1,11 +0,0 @@
export interface WebsocketConsumerStatusMessage {
filename?: string
task_id?: string
current_progress?: number
max_progress?: number
status?: string
message?: string
document_id: number
}
Whitespace-only changes.
@@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { ConsumerStatusService } from './consumer-status.service';
describe('ConsumerStatusService', () => {
let service: ConsumerStatusService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ConsumerStatusService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -1,195 +0,0 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { environment } from 'src/environments/environment';
import { WebsocketConsumerStatusMessage } from '../data/websocket-consumer-status-message';
export enum FileStatusPhase {
STARTED = 0,
UPLOADING = 1,
PROCESSING = 2,
SUCCESS = 3,
FAILED = 4
}
export const FILE_STATUS_MESSAGES = {
"document_already_exists": $localize`Document already exists.`,
"file_not_found": $localize`File not found.`,
"pre_consume_script_not_found": $localize`:Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation:Pre-consume script does not exist.`,
"pre_consume_script_error": $localize`:Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation:Error while executing pre-consume script.`,
"post_consume_script_not_found": $localize`:Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation:Post-consume script does not exist.`,
"post_consume_script_error": $localize`:Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation:Error while executing post-consume script.`,
"new_file": $localize`Received new file.`,
"unsupported_type": $localize`File type not supported.`,
"parsing_document": $localize`Processing document...`,
"generating_thumbnail": $localize`Generating thumbnail...`,
"parse_date": $localize`Retrieving date from document...`,
"save_document": $localize`Saving document...`,
"finished": $localize`Finished.`
}
export class FileStatus {
filename: string
taskId: string
phase: FileStatusPhase = FileStatusPhase.STARTED
currentPhaseProgress: number
currentPhaseMaxProgress: number
message: string
documentId: number
getProgress(): number {
switch (this.phase) {
case FileStatusPhase.STARTED:
return 0.0
case FileStatusPhase.UPLOADING:
return this.currentPhaseProgress / this.currentPhaseMaxProgress * 0.2
case FileStatusPhase.PROCESSING:
return (this.currentPhaseProgress / this.currentPhaseMaxProgress * 0.8) + 0.2
case FileStatusPhase.SUCCESS:
case FileStatusPhase.FAILED:
return 1.0
}
}
updateProgress(status: FileStatusPhase, currentProgress?: number, maxProgress?: number) {
if (status >= this.phase) {
this.phase = status
if (currentProgress != null) {
this.currentPhaseProgress = currentProgress
}
if (maxProgress != null) {
this.currentPhaseMaxProgress = maxProgress
}
}
}
}
@Injectable({
providedIn: 'root'
})
export class ConsumerStatusService {
constructor() { }
private statusWebSocket: WebSocket
private consumerStatus: FileStatus[] = []
private documentDetectedSubject = new Subject<FileStatus>()
private documentConsumptionFinishedSubject = new Subject<FileStatus>()
private documentConsumptionFailedSubject = new Subject<FileStatus>()
private get(taskId: string, filename?: string) {
let status = this.consumerStatus.find(e => e.taskId == taskId) || this.consumerStatus.find(e => e.filename == filename && e.taskId == null)
let created = false
if (!status) {
status = new FileStatus()
this.consumerStatus.push(status)
created = true
}
status.taskId = taskId
status.filename = filename
return {'status': status, 'created': created}
}
newFileUpload(filename: string): FileStatus {
let status = new FileStatus()
status.filename = filename
this.consumerStatus.push(status)
return status
}
getConsumerStatus(phase?: FileStatusPhase) {
if (phase != null) {
return this.consumerStatus.filter(s => s.phase == phase)
} else {
return this.consumerStatus
}
}
getConsumerStatusNotCompleted() {
return this.consumerStatus.filter(s => s.phase < FileStatusPhase.SUCCESS)
}
getConsumerStatusCompleted() {
return this.consumerStatus.filter(s => s.phase == FileStatusPhase.FAILED || s.phase == FileStatusPhase.SUCCESS)
}
connect() {
this.disconnect()
this.statusWebSocket = new WebSocket(`${environment.webSocketProtocol}//${environment.webSocketHost}/ws/status/`);
this.statusWebSocket.onmessage = (ev) => {
let statusMessage: WebsocketConsumerStatusMessage = JSON.parse(ev['data'])
let statusMessageGet = this.get(statusMessage.task_id, statusMessage.filename)
let status = statusMessageGet.status
let created = statusMessageGet.created
status.updateProgress(FileStatusPhase.PROCESSING, statusMessage.current_progress, statusMessage.max_progress)
if (statusMessage.message && statusMessage.message in FILE_STATUS_MESSAGES) {
status.message = FILE_STATUS_MESSAGES[statusMessage.message]
} else if (statusMessage.message) {
status.message = statusMessage.message
}
status.documentId = statusMessage.document_id
if (created && statusMessage.status == 'STARTING') {
this.documentDetectedSubject.next(status)
}
if (statusMessage.status == "SUCCESS") {
status.phase = FileStatusPhase.SUCCESS
this.documentConsumptionFinishedSubject.next(status)
}
if (statusMessage.status == "FAILED") {
status.phase = FileStatusPhase.FAILED
this.documentConsumptionFailedSubject.next(status)
}
}
}
fail(status: FileStatus, message: string) {
status.message = message
status.phase = FileStatusPhase.FAILED
this.documentConsumptionFailedSubject.next(status)
}
disconnect() {
if (this.statusWebSocket) {
this.statusWebSocket.close()
this.statusWebSocket = null
}
}
dismiss(status: FileStatus) {
let index = this.consumerStatus.findIndex(s => s.filename == status.filename)
if (index > -1) {
this.consumerStatus.splice(index, 1)
}
}
dismissCompleted() {
this.consumerStatus = this.consumerStatus.filter(status => status.phase != FileStatusPhase.SUCCESS)
}
onDocumentConsumptionFinished() {
return this.documentConsumptionFinishedSubject
}
onDocumentConsumptionFailed() {
return this.documentConsumptionFailedSubject
}
onDocumentDetected() {
return this.documentDetectedSubject
}
}
@@ -11,7 +11,6 @@ import { CorrespondentService } from './correspondent.service';
import { DocumentTypeService } from './document-type.service';
import { TagService } from './tag.service';
import { FILTER_RULE_TYPES } from 'src/app/data/filter-rule-type';
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions';
export const DOCUMENT_SORT_FIELDS = [
{ field: 'archive_serial_number', name: $localize`ASN` },
@@ -130,8 +129,4 @@ export class DocumentService extends AbstractPaperlessService<PaperlessDocument>
return this.http.post<SelectionData>(this.getResourceUrl(null, 'selection_data'), {"documents": ids})
}
getSuggestions(id: number): Observable<PaperlessDocumentSuggestions> {
return this.http.get<PaperlessDocumentSuggestions>(this.getResourceUrl(id, 'suggestions'))
}
}
+5 -12
View File
@@ -1,21 +1,14 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
import { PaperlessLog } from 'src/app/data/paperless-log';
import { AbstractPaperlessService } from './abstract-paperless-service';
@Injectable({
providedIn: 'root'
})
export class LogService {
export class LogService extends AbstractPaperlessService<PaperlessLog> {
constructor(private http: HttpClient) {
}
list(): Observable<string[]> {
return this.http.get<string[]>(`${environment.apiBaseUrl}logs/`)
}
get(id: string): Observable<string[]> {
return this.http.get<string[]>(`${environment.apiBaseUrl}logs/${id}/`)
constructor(http: HttpClient) {
super(http, 'logs')
}
}
+2 -10
View File
@@ -23,11 +23,7 @@ export const SETTINGS_KEYS = {
DARK_MODE_ENABLED: 'general-settings:dark-mode:enabled',
USE_NATIVE_PDF_VIEWER: 'general-settings:document-details:native-pdf-viewer',
DATE_LOCALE: 'general-settings:date-display:date-locale',
DATE_FORMAT: 'general-settings:date-display:date-format',
NOTIFICATIONS_CONSUMER_NEW_DOCUMENT: 'general-settings:notifications:consumer-new-documents',
NOTIFICATIONS_CONSUMER_SUCCESS: 'general-settings:notifications:consumer-success',
NOTIFICATIONS_CONSUMER_FAILED: 'general-settings:notifications:consumer-failed',
NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD: 'general-settings:notifications:consumer-suppress-on-dashboard',
DATE_FORMAT: 'general-settings:date-display:date-format'
}
const SETTINGS: PaperlessSettings[] = [
@@ -38,11 +34,7 @@ const SETTINGS: PaperlessSettings[] = [
{key: SETTINGS_KEYS.DARK_MODE_ENABLED, type: "boolean", default: false},
{key: SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, type: "boolean", default: false},
{key: SETTINGS_KEYS.DATE_LOCALE, type: "string", default: ""},
{key: SETTINGS_KEYS.DATE_FORMAT, type: "string", default: "mediumDate"},
{key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT, type: "boolean", default: true},
{key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS, type: "boolean", default: true},
{key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, type: "boolean", default: true},
{key: SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, type: "boolean", default: true},
{key: SETTINGS_KEYS.DATE_FORMAT, type: "string", default: "mediumDate"}
]
@Injectable({
-4
View File
@@ -9,10 +9,6 @@ export interface Toast {
delay: number
action?: any
actionName?: string
}
@Injectable({
+1 -3
View File
@@ -2,7 +2,5 @@ export const environment = {
production: true,
apiBaseUrl: "/api/",
appTitle: "Paperless-ng",
version: "1.1.1",
webSocketHost: window.location.host,
webSocketProtocol: (window.location.protocol == "https:" ? "wss:" : "ws:")
version: "1.0.0"
};
+1 -3
View File
@@ -6,9 +6,7 @@ export const environment = {
production: false,
apiBaseUrl: "http://localhost:8000/api/",
appTitle: "Paperless-ng",
version: "DEVELOPMENT",
webSocketHost: "localhost:8000",
webSocketProtocol: "ws:"
version: "DEVELOPMENT"
};
/*
+88 -366
View File
@@ -1,60 +1,12 @@
<?xml version="1.0" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file datatype="plaintext" original="ng2.template" source-language="en-US" target-language="de">
<body>
<trans-unit datatype="html" id="9103526311244275943">
<source>Document added</source>
<target>Dokument hinzugefügt</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="9204248378636247318">
<source>Document <x equiv-text="status.filename" id="PH"/> was added to paperless.</source>
<target>Das Dokument <x equiv-text="status.filename" id="PH"/> wurde zu Paperless hinzugefügt.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1931214133925051574">
<source>Open document</source>
<target>Dokument öffnen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8582620835547864448">
<source>Could not add <x equiv-text="status.filename" id="PH"/>: <x equiv-text="status.message" id="PH_1"/></source>
<target>Konnte <x equiv-text="status.filename" id="PH"/> nicht hinzufügen: <x equiv-text="status.message" id="PH_1"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1710712016675379662">
<source>New document detected</source>
<target>Neues Dokument erkannt</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="587031278561344416">
<source>Document <x equiv-text="status.filename" id="PH"/> is being processed by paperless.</source>
<target>Dokument <x equiv-text="status.filename" id="PH"/> wird von Paperless verarbeitet.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="4733307402565258070">
<source>Documents</source>
<target>Dokumente</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2155249406916744630">
@@ -62,7 +14,7 @@
<target>Ansicht &quot;<x equiv-text="this.list.savedView.name" id="PH"/>&quot; erfolgreich gespeichert.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">109</context>
<context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6837554170707123455">
@@ -70,7 +22,7 @@
<target>Ansicht &quot;<x equiv-text="savedView.name" id="PH"/>&quot; erfolgreich erstellt.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">130</context>
<context context-type="linenumber">115</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="9ca82952a6bc860b5391d5975322d8af8ceddfa4">
@@ -195,7 +147,7 @@
</trans-unit>
<trans-unit datatype="html" id="1b051734b0ee9021991c91b3ed4e81c244322462">
<source>Created</source>
<target>Ausgestellt</target>
<target>Erstellt</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">129</context>
@@ -214,7 +166,7 @@
<target>Löschen bestätigen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">203</context>
<context context-type="linenumber">192</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5382975254277698192">
@@ -222,7 +174,7 @@
<target>Möchten Sie das Dokument &quot;<x equiv-text="this.document.title" id="PH"/>&quot; wirklich löschen?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">204</context>
<context context-type="linenumber">193</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6691075929777935948">
@@ -230,7 +182,7 @@
<target>Die Dateien dieses Dokuments werden permanent gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">205</context>
<context context-type="linenumber">194</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="719892092227206532">
@@ -238,7 +190,7 @@
<target>Dokument löschen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">207</context>
<context context-type="linenumber">196</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1844801255494293730">
@@ -246,7 +198,7 @@
<target>Fehler beim Löschen des Dokuments: <x equiv-text="JSON.stringify(error)" id="PH"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">203</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="826b25211922a1b46436589233cb6f1a163d89b7">
@@ -355,7 +307,7 @@
</trans-unit>
<trans-unit datatype="html" id="30eebc2dd656dbcb259d8d5286d244ee397d63bd">
<source>Date created</source>
<target>Ausgestellt am</target>
<target>Erstellt am</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">61</context>
@@ -427,7 +379,7 @@
</trans-unit>
<trans-unit datatype="html" id="203c7adb5bd74ae18e84e1e3b66ddfe76bc0023f">
<source>Original document metadata</source>
<target>Metadaten Original</target>
<target>Original-Metadaten</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">121</context>
@@ -435,7 +387,7 @@
</trans-unit>
<trans-unit datatype="html" id="7174dcea4a3d0b743e7e4a78747e902230727f67">
<source>Archived document metadata</source>
<target>Metadaten Archiv</target>
<target>Metadaten des archivierten Dokuments</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">122</context>
@@ -451,7 +403,7 @@
</trans-unit>
<trans-unit datatype="html" id="8765497970646365998">
<source>Hello <x equiv-text="this.displayName" id="PH"/>, welcome to Paperless-ng!</source>
<target>Hallo <x equiv-text="this.displayName" id="PH"/>, willkommen zu Paperless-ng!</target>
<target>Hallo <x equiv-text="this.displayName" id="PH"/>, Willkommen zu Paperless-ng</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
<context context-type="linenumber">33</context>
@@ -598,7 +550,7 @@
<target>Gespeicherte Ansicht &quot;<x equiv-text="savedView.name" id="PH"/>&quot; gelöscht.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">67</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5647210819299459618">
@@ -606,7 +558,7 @@
<target>Einstellungen erfolgreich gespeichert.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">87</context>
<context context-type="linenumber">79</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6839066544204061364">
@@ -614,7 +566,7 @@
<target>Benutze Systemsprache</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">91</context>
<context context-type="linenumber">83</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7729897675462249787">
@@ -622,7 +574,7 @@
<target>Benutze Datumsformat der Anzeigesprache</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">87</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8488620293789898901">
@@ -630,7 +582,7 @@
<target>Fehler beim Speichern der Einstellungen auf dem Server: <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">111</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="121cc5391cd2a5115bc2b3160379ee5b36cd7716">
@@ -649,20 +601,12 @@
<context context-type="linenumber">10</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab">
<source>Notifications</source>
<target>Benachrichtigungen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">115</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="99dee94e92dbd9e21a008d4569f9719ed206ae37">
<source>Saved views</source>
<target>Gespeicherte Ansichten</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">133</context>
<context context-type="linenumber">114</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="bbe41ac2ea4a6c00ea941a41b33105048f8e9f13">
@@ -817,60 +761,12 @@
<context context-type="linenumber">107</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8680abbea249ebe9c2fe35556559c8e1a9eb5841">
<source>Document processing</source>
<target>Dokumentverarbeitung</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">118</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2ad4d76b36341c589d94004ad2a213fd4d6f5ca0">
<source>Show notifications when new documents are detected</source>
<target>Zeige Benachrichtigungen wenn neue Dokumente erkannt werden</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">122</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e775f4f7c40249d31426ae61a21616a0c9d8e84f">
<source>Show notifications when document processing completes successfully</source>
<target>Zeige Benachrichtigungen wenn neue Dokumente erfolgreich hinzugefügt wurden</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e3844dd174d8e817ddb551fae28f14ae80ca36b6">
<source>Show notifications when document processing fails</source>
<target>Zeige Benachrichtigungen wenn Dokumente nicht hinzugefügt werden konnten</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">124</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="af113f7c9f7e13145c3461f61a1aedf12d57bd71">
<source>Suppress notifications on dashboard</source>
<target>Unterdrücke Benachrichtigungen auf der Startseite.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e27bd3804d2936a6897e81c2e52e294490e5e5a8">
<source>This will suppress all messages about document processing status on the dashboard.</source>
<target>Dadurch werden alle Benachrichtigungen über die Dokumentenverarbeitung auf der Startseite unterdrückt.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8cb90334f5dfd7fc67205085f59381e2a334ccfc">
<source>Appears on</source>
<target>Erscheint auf</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">145</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6717cf1acf04728fc2b7c39f6d3297f8ff15fde5">
@@ -878,7 +774,7 @@
<target>Auf Startseite zeigen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">148</context>
<context context-type="linenumber">129</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="541bfc5b123b3f8867fd681eaceefb663a811973">
@@ -886,7 +782,7 @@
<target>In Seitenleiste zeigen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">152</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="abba764a7a595d04dc8c3b26e04b3780d4fdb540">
@@ -894,7 +790,7 @@
<target>Keine gespeicherten Ansichten vorhanden.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">143</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="ef60a738a565f498b858e903e42bc5ffc3cc1299">
@@ -1119,7 +1015,7 @@
<target>Verwalten</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">107</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="408cb6073e60c5d966296a3207fc596adca75e01">
@@ -1127,15 +1023,15 @@
<target>Administration</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">149</context>
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="321e4419a943044e674beb55b8039f42a9761ca5">
<source>Info</source>
<target>Info</target>
<trans-unit datatype="html" id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
<source>Misc</source>
<target>Weiteres</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">155</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7">
@@ -1143,7 +1039,7 @@
<target>Dokumentation</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">167</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="355a222236bc01b9a8cd3cb9ecf76891125aed69">
@@ -1151,15 +1047,7 @@
<target>GitHub</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">170</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="ea3a452c5238897cabc5781308cceb2d37dcf258">
<source>Suggest an idea</source>
<target>Eine Idee vorschlagen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">176</context>
<context context-type="linenumber">174</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="af665f8de8fabe306aaf27443957e69bcbbce63c">
@@ -1175,7 +1063,7 @@
<target>Geöffnete Dokumente</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">87</context>
<context context-type="linenumber">92</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="dca5bf9344a759fa5a07f1b21f50286ec242ba44">
@@ -1183,7 +1071,7 @@
<target>Alle schließen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">101</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5195932016807797291">
@@ -1395,14 +1283,6 @@
<context context-type="linenumber">73</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7894972847287473517">
<source>&quot;<x equiv-text="items[0].name" id="PH"/>&quot;</source>
<target>&quot;<x equiv-text="items[0].name" id="PH"/>&quot;</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8639884465898458690">
<source>&quot;<x equiv-text="items[0].name" id="PH"/>&quot; and &quot;<x equiv-text="items[1].name" id="PH_1"/>&quot;</source>
<target>&quot;<x equiv-text="items[0].name" id="PH"/>&quot; und &quot;<x equiv-text="items[1].name" id="PH_1"/>&quot;</target>
@@ -1412,6 +1292,14 @@
</context-group>
<note from="description" priority="1">This is for messages like 'modify &quot;tag1&quot; and &quot;tag2&quot;'</note>
</trans-unit>
<trans-unit datatype="html" id="7894972847287473517">
<source>&quot;<x equiv-text="i.name" id="PH"/>&quot;</source>
<target>&quot;<x equiv-text="i.name" id="PH"/>&quot;</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">116</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="760986369763309193">
<source>, </source>
<target>, </target>
@@ -1582,14 +1470,6 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e">
<source>Suggestions:</source>
<target>Vorschläge:</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="27d158b47717ff9305d19866960418c603f19d55">
<source>Save current view</source>
<target>Aktuelle Ansicht speichern</target>
@@ -1630,60 +1510,28 @@
<context context-type="linenumber">4</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6443586946875325554">
<source>Processing: <x equiv-text="countUploadingAndProcessing" id="PH"/></source>
<target>Verarbeite: <x equiv-text="countUploadingAndProcessing" id="PH"/></target>
<trans-unit datatype="html" id="8705589528094706681">
<source>The document has been uploaded and will be processed by the consumer shortly.</source>
<target>Das Dokument wurde hochgeladen und wird in Kürze durch Paperless-ng verarbeitet.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">32</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="9182918211699394982">
<source>Failed: <x equiv-text="countFailed" id="PH"/></source>
<target>Fehlgeschlagen: <x equiv-text="countFailed" id="PH"/></target>
<trans-unit datatype="html" id="4956689020592747108">
<source>There was an error while uploading the document: <x equiv-text="error.error.document" id="PH"/></source>
<target>Konnte Dokument nicht hochladen: <x equiv-text="error.error.document" id="PH"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">71</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="534116346205124059">
<source>Added: <x equiv-text="countSuccess" id="PH"/></source>
<target>Hinzugefügt: <x equiv-text="countSuccess" id="PH"/></target>
<trans-unit datatype="html" id="7554858521017940575">
<source>An error has occurred while uploading the document. Sorry!</source>
<target>Beim Hochladen trat ein Fehler auf. Entschuldigung!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">38</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3852289441366561594">
<source>Connecting...</source>
<target>Verbinde...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">118</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1245343823699368872">
<source>Uploading...</source>
<target>Hochladen...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7446520539098045935">
<source>Upload complete, waiting...</source>
<target>Datei hochgeladen, warte...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1405142710727603568">
<source>HTTP error: <x equiv-text="error.status" id="PH"/> <x equiv-text="error.statusText" id="PH_1"/></source>
<target>HTTP-Fehler: <x equiv-text="error.status" id="PH"/> <x equiv-text="error.statusText" id="PH_1"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">136</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e022072b3e4dd77e3f09960817ef3359a49963b3">
@@ -1699,43 +1547,25 @@
<target>Dokumente hier ablegen oder</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">13</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="865c511f4a24558ed0e954f9bbbff557bbb8954d">
<source>Browse files</source>
<target>Datei auswählen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="33c76d75ce25ce3b05ab22877f1b6b09dcf603ae">
<source>{VAR_PLURAL, plural, =1 {Uploading file...} =other {Uploading <x id="INTERPOLATION"/> files...}}</source>
<target>{VAR_PLURAL, plural, =1 {Lade Datei hoch...} =other {Lade <x id="INTERPOLATION"/> Dateien hoch...}}</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="bd4a8607e4a002d939cffb347ec056664dfb2c73">
<source>Dismiss completed</source>
<target>Fertiggestellte verbergen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<note from="description" priority="1">This button dismisses all status messages about processed documents on the dashboard (failed and successful)</note>
</trans-unit>
<trans-unit datatype="html" id="90917e1a0a7bb59e9d11bdde9183e9391963e17b">
<source>{VAR_PLURAL, plural, =1 {One more document} other {<x id="INTERPOLATION"/> more documents}}</source>
<target>{VAR_PLURAL, plural, =1 {Ein weiteres Dokument} other {<x id="INTERPOLATION"/> weitere Dokumente}}</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">25</context>
</context-group>
<note from="description" priority="1">This is shown as a summary line when there are more than 5 document in the processing pipeline.</note>
</trans-unit>
<trans-unit datatype="html" id="710254a196a2649674438edf8a15b7ab1f48271b">
<source>Open document</source>
<target>Dokument öffnen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">45</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="45854ddec74086b271e62be6a363f4fa5036f7fc">
<source>First steps</source>
<target>Erste Schritte</target>
@@ -1754,7 +1584,7 @@
</trans-unit>
<trans-unit datatype="html" id="ea8d9a9486d5639d1c38c012900b8d34d5e4135d">
<source>You can start uploading documents by dropping them in the file upload box to the right or by dropping them in the configured consumption folder and they'll start showing up in the documents list. After you've added some metadata to your documents, use the filtering mechanisms of paperless to create custom views (such as 'Recently added', 'Tagged TODO') and they will appear on the dashboard instead of this message.</source>
<target>Sie können Ihre Dokumente hochladen, indem Sie sie in die Hochladebox auf der rechten Seite ziehen oder in den konfigurierten Ablageordner kopieren/verschieben. Danach werden sie in der Dokumentenliste erscheinen. Nachdem Sie den Dokumenten einige Metadaten zugewiesen haben, können Sie die Filtermechanismen zur Erstellung individueller Ansichten benutzen (zum Beispiel &quot;Kürzlich hinzugefügt&quot; oder &quot;Mit TODO markiert&quot;) und die Ansichten werden auf der Startseite anstelle dieser Nachricht erscheinen.</target>
<target>Sie können Ihre Dokumente hochladen, indem Sie sie in die Hochladebox auf der rechten Seite ziehen oder in den konfigurierten Ablageordner kopieren/verschieben. Danach werden sie inder Dokumentliste erscheinen. Nachdem Sie den Dokumenten einige Metadaten zugewiesen haben, können Sie die Filtermechanismen zur Erstellung individueller Ansichten benutzen (zum Beispiel &quot;Kürzlich hinzugefügt&quot; oder &quot;Mit TODO markiert&quot;) und die Ansichten werden auf der Startseite erscheinen und diese Nachricht ersetzen.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/welcome-widget/welcome-widget.component.html</context>
<context context-type="linenumber">6,7</context>
@@ -1770,7 +1600,7 @@
</trans-unit>
<trans-unit datatype="html" id="edb41dc36af2e70789d8e3422325d930e783253e">
<source>Once you've got a couple documents in paperless and added metadata to them, paperless can assign that metadata to new documents automatically.</source>
<target>Nachdem Sie einige Dokumente hochgeladen und diesen Korrespondenten, Tags und Dokumenttypen zugewiesen haben, kann Paperless-ng diese Metadaten automatisch passenden neuen Dokumenten zuweisen.</target>
<target>Nachdem Sie einige Dokumente hochgeladen und mit Metadatebn haben, kann Paperless diese Metadaten automatisch passenden neuen Dokumenten zuweisen.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/welcome-widget/welcome-widget.component.html</context>
<context context-type="linenumber">10</context>
@@ -1778,7 +1608,7 @@
</trans-unit>
<trans-unit datatype="html" id="9dca488928c0b7af46c3866f86a6c2c8548399aa">
<source>You can configure paperless to read your mails and add documents from attached files.</source>
<target>Sie können in Paperless Ihre E-Mail-Konten einrichten und Paperless wird automatisch Dokumente aus Dateianhängen hinzufügen.</target>
<target>Sie können in Paperless Ihre E-Mail-Konten einrichten und Paperless wird automatisch Dokumente aus Dateianhängen erzeugen.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/welcome-widget/welcome-widget.component.html</context>
<context context-type="linenumber">11</context>
@@ -1845,7 +1675,7 @@
<target>Englisch (US)</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">82</context>
<context context-type="linenumber">74</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1858110241312746425">
@@ -1853,7 +1683,7 @@
<target>Deutsch</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">83</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3071065188816255493">
@@ -1861,7 +1691,7 @@
<target>Niederländisch</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">84</context>
<context context-type="linenumber">76</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7633754075223722162">
@@ -1869,115 +1699,7 @@
<target>Französisch</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">85</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2119857572761283468">
<source>Document already exists.</source>
<target>Dokument existiert bereits.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">15</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="148389968432135849">
<source>File not found.</source>
<target>Datei nicht gefunden.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1520671543092565667">
<source>Pre-consume script does not exist.</source>
<target>Pre-Consume-Skript existiert nicht.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">17</context>
</context-group>
<note from="description" priority="1">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="7742915911032564889">
<source>Error while executing pre-consume script.</source>
<target>Fehler beim Ausführen des Pre-Consume-Skripts.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">18</context>
</context-group>
<note from="description" priority="1">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="8995193730018060346">
<source>Post-consume script does not exist.</source>
<target>Post-Consume-Skript existiert nicht.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">19</context>
</context-group>
<note from="description" priority="1">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="256773668518189604">
<source>Error while executing post-consume script.</source>
<target>Fehler beim Ausführen des Post-Consume-Skripts.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">20</context>
</context-group>
<note from="description" priority="1">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="6252258095055634191">
<source>Received new file.</source>
<target>Neue Datei erhalten.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7337565919209746135">
<source>File type not supported.</source>
<target>Dateityp wird nicht unterstützt.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5002399167376099234">
<source>Processing document...</source>
<target>Verarbeite Dokument...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1085975194762600381">
<source>Generating thumbnail...</source>
<target>Erzeuge Miniaturbild...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3280851677698431426">
<source>Retrieving date from document...</source>
<target>Ermittle Datum des Dokuments...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7162102384876037296">
<source>Saving document...</source>
<target>Speichere Dokument...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="4550450765009165976">
<source>Finished.</source>
<target>Abgeschlossen.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">27</context>
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1519954996184640001">
@@ -1985,7 +1707,7 @@
<target>Fehler</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5037437391296624618">
@@ -1993,7 +1715,7 @@
<target>Information</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">39</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7517688192215738656">
@@ -2001,7 +1723,7 @@
<target>ASN</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">17</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2691296884221415710">
@@ -2009,7 +1731,7 @@
<target>Korrespondent</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">18</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5701618810648052610">
@@ -2017,7 +1739,7 @@
<target>Titel</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">19</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5066119607229701477">
@@ -2025,15 +1747,15 @@
<target>Dokumenttyp</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">20</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="4207916966377787111">
<source>Created</source>
<target>Ausgestellt am</target>
<target>Erstellt am</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">21</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="231679111972850796">
@@ -2041,7 +1763,7 @@
<target>Hinzugefügt am</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">22</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3553216189604488439">
@@ -2049,12 +1771,12 @@
<target>Geändert am</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">23</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2056433880533904076">
<source>Light blue</source>
<target>Blau, hell</target>
<target>Hellblau</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">6</context>
@@ -2070,7 +1792,7 @@
</trans-unit>
<trans-unit datatype="html" id="1143414876575720034">
<source>Light green</source>
<target>Grün, hell</target>
<target>Hellgrün</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">8</context>
@@ -2086,7 +1808,7 @@
</trans-unit>
<trans-unit datatype="html" id="3250646524116252719">
<source>Light red</source>
<target>Rot, hell</target>
<target>Hellrot</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">10</context>
@@ -2102,7 +1824,7 @@
</trans-unit>
<trans-unit datatype="html" id="5479028842846122610">
<source>Light orange</source>
<target>Orange, hell</target>
<target>Hellorange</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">12</context>
@@ -2118,7 +1840,7 @@
</trans-unit>
<trans-unit datatype="html" id="1789283185177957430">
<source>Light violet</source>
<target>Violet, hell</target>
<target>Hellviolet</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">14</context>
@@ -2150,7 +1872,7 @@
</trans-unit>
<trans-unit datatype="html" id="461048771215121187">
<source>Light grey</source>
<target>Grau, hell</target>
<target>Hellgrau</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/paperless-tag.ts</context>
<context context-type="linenumber">18</context>
@@ -2230,7 +1952,7 @@
</trans-unit>
<trans-unit datatype="html" id="7517655726614958140">
<source>Any: Document contains any of these words (space separated)</source>
<target>Irgendein Wort: Dokument enthält eins der folgenden Wörter</target>
<target>Irgendein Wort: Dokument enthält eins der folgender Wörter</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/matching-model.ts</context>
<context context-type="linenumber">12</context>
@@ -2278,7 +2000,7 @@
</trans-unit>
<trans-unit datatype="html" id="7548151332424148033">
<source>Regular expression: Document matches this regular expression</source>
<target>Regulärer Ausdruck: Dokument passt zum folgenden Ausdruck</target>
<target>Regulärer Ausdruck: Dokument passt zum folgenden Audruck</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/matching-model.ts</context>
<context context-type="linenumber">15</context>
@@ -2294,7 +2016,7 @@
</trans-unit>
<trans-unit datatype="html" id="8419167206585286450">
<source>Fuzzy: Document contains a word similar to this word</source>
<target>Ungenau: Dokument enthält ein zum folgenden Wort ähnliches Wort</target>
<target>Ungenau: Dokument enthält ein zum folgendem Wort ähnliches Wort</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/data/matching-model.ts</context>
<context context-type="linenumber">16</context>
File diff suppressed because it is too large. Load diff
+70 -348
View File
@@ -1,60 +1,12 @@
<?xml version="1.0" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file datatype="plaintext" original="ng2.template" source-language="en-US" target-language="fr">
<body>
<trans-unit datatype="html" id="9103526311244275943">
<source>Document added</source>
<target>Document ajouté</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="9204248378636247318">
<source>Document <x equiv-text="status.filename" id="PH"/> was added to paperless.</source>
<target>Le document <x equiv-text="status.filename" id="PH"/> a été ajouté à Paperless-ng.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1931214133925051574">
<source>Open document</source>
<target>Ouvrir le document</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8582620835547864448">
<source>Could not add <x equiv-text="status.filename" id="PH"/>: <x equiv-text="status.message" id="PH_1"/></source>
<target>Impossible d'ajouter <x equiv-text="status.filename" id="PH"/> : <x equiv-text="status.message" id="PH_1"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1710712016675379662">
<source>New document detected</source>
<target>Nouveau document détecté</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="587031278561344416">
<source>Document <x equiv-text="status.filename" id="PH"/> is being processed by paperless.</source>
<target>Le document <x equiv-text="status.filename" id="PH"/> est en cours de traitement par Paperless-ng.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="4733307402565258070">
<source>Documents</source>
<target>Documents</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2155249406916744630">
@@ -62,7 +14,7 @@
<target>Vue &quot;<x equiv-text="this.list.savedView.name" id="PH"/>&quot; enregistrée avec succès.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">109</context>
<context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6837554170707123455">
@@ -70,7 +22,7 @@
<target>Vue &quot;<x equiv-text="savedView.name" id="PH"/>&quot; créée avec succès.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">130</context>
<context context-type="linenumber">115</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="9ca82952a6bc860b5391d5975322d8af8ceddfa4">
@@ -214,7 +166,7 @@
<target>Confirmer la suppression</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">203</context>
<context context-type="linenumber">192</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5382975254277698192">
@@ -222,7 +174,7 @@
<target>Voulez-vous vraiment supprimer le document &quot;<x equiv-text="this.document.title" id="PH"/>&quot; ?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">204</context>
<context context-type="linenumber">193</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6691075929777935948">
@@ -230,7 +182,7 @@
<target>Les fichiers liés à ce document seront supprimés définitivement. Cette action est irréversible.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">205</context>
<context context-type="linenumber">194</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="719892092227206532">
@@ -238,7 +190,7 @@
<target>Supprimer le document</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">207</context>
<context context-type="linenumber">196</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1844801255494293730">
@@ -246,7 +198,7 @@
<target>Une erreur s'est produite lors de la suppression du document : <x equiv-text="JSON.stringify(error)" id="PH"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">203</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="826b25211922a1b46436589233cb6f1a163d89b7">
@@ -598,7 +550,7 @@
<target>Vue &quot;<x equiv-text="savedView.name" id="PH"/>&quot; supprimée.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">67</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5647210819299459618">
@@ -606,7 +558,7 @@
<target>Paramètres enregistrés avec succès.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">87</context>
<context context-type="linenumber">79</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6839066544204061364">
@@ -614,7 +566,7 @@
<target>Utiliser la langue du système</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">91</context>
<context context-type="linenumber">83</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7729897675462249787">
@@ -622,7 +574,7 @@
<target>Utiliser le format de date de la langue d'affichage</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">87</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8488620293789898901">
@@ -630,7 +582,7 @@
<target>Une erreur s'est produite lors de l'enregistrement des paramètres sur le serveur : <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">111</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="121cc5391cd2a5115bc2b3160379ee5b36cd7716">
@@ -649,20 +601,12 @@
<context context-type="linenumber">10</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab">
<source>Notifications</source>
<target>Notifications</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">115</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="99dee94e92dbd9e21a008d4569f9719ed206ae37">
<source>Saved views</source>
<target>Vues enregistrées</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">133</context>
<context context-type="linenumber">114</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="bbe41ac2ea4a6c00ea941a41b33105048f8e9f13">
@@ -817,60 +761,12 @@
<context context-type="linenumber">107</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8680abbea249ebe9c2fe35556559c8e1a9eb5841">
<source>Document processing</source>
<target>Traitement de document</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">118</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2ad4d76b36341c589d94004ad2a213fd4d6f5ca0">
<source>Show notifications when new documents are detected</source>
<target>Afficher des notifications lorsque de nouveaux documents sont détectés</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">122</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e775f4f7c40249d31426ae61a21616a0c9d8e84f">
<source>Show notifications when document processing completes successfully</source>
<target>Afficher des notifications lorsque le traitement des documents se termine avec succès</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e3844dd174d8e817ddb551fae28f14ae80ca36b6">
<source>Show notifications when document processing fails</source>
<target>Afficher des notifications en cas d'échec du traitement des documents</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">124</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="af113f7c9f7e13145c3461f61a1aedf12d57bd71">
<source>Suppress notifications on dashboard</source>
<target>Supprimer les notifications du tableau de bord</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e27bd3804d2936a6897e81c2e52e294490e5e5a8">
<source>This will suppress all messages about document processing status on the dashboard.</source>
<target>Cela supprimera tous les messages liés au traitement de documents sur le tableau de bord.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8cb90334f5dfd7fc67205085f59381e2a334ccfc">
<source>Appears on</source>
<target>Apparaît sur</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">145</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6717cf1acf04728fc2b7c39f6d3297f8ff15fde5">
@@ -878,7 +774,7 @@
<target>Montrer sur le tableau de bord</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">148</context>
<context context-type="linenumber">129</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="541bfc5b123b3f8867fd681eaceefb663a811973">
@@ -886,7 +782,7 @@
<target>Montrer dans la barre latérale</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">152</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="abba764a7a595d04dc8c3b26e04b3780d4fdb540">
@@ -894,7 +790,7 @@
<target>Aucune vue sauvegardée n'est définie.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">143</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="ef60a738a565f498b858e903e42bc5ffc3cc1299">
@@ -1119,7 +1015,7 @@
<target>Gestion</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">107</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="408cb6073e60c5d966296a3207fc596adca75e01">
@@ -1127,15 +1023,15 @@
<target>Administration</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">149</context>
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="321e4419a943044e674beb55b8039f42a9761ca5">
<source>Info</source>
<target>Info</target>
<trans-unit datatype="html" id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
<source>Misc</source>
<target>Divers</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">155</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7">
@@ -1143,7 +1039,7 @@
<target>Documentation</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">167</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="355a222236bc01b9a8cd3cb9ecf76891125aed69">
@@ -1151,15 +1047,7 @@
<target>GitHub</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">170</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="ea3a452c5238897cabc5781308cceb2d37dcf258">
<source>Suggest an idea</source>
<target>Suggérer une idée</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">176</context>
<context context-type="linenumber">174</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="af665f8de8fabe306aaf27443957e69bcbbce63c">
@@ -1175,7 +1063,7 @@
<target>Documents ouverts</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">87</context>
<context context-type="linenumber">92</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="dca5bf9344a759fa5a07f1b21f50286ec242ba44">
@@ -1183,7 +1071,7 @@
<target>Fermer tout</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">101</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5195932016807797291">
@@ -1395,14 +1283,6 @@
<context context-type="linenumber">73</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7894972847287473517">
<source>&quot;<x equiv-text="items[0].name" id="PH"/>&quot;</source>
<target>&quot;<x equiv-text="items[0].name" id="PH"/>&quot;</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8639884465898458690">
<source>&quot;<x equiv-text="items[0].name" id="PH"/>&quot; and &quot;<x equiv-text="items[1].name" id="PH_1"/>&quot;</source>
<target>&quot;<x equiv-text="items[0].name" id="PH"/>&quot; et &quot;<x equiv-text="items[1].name" id="PH_1"/>&quot;</target>
@@ -1412,6 +1292,14 @@
</context-group>
<note from="description" priority="1">This is for messages like 'modify &quot;tag1&quot; and &quot;tag2&quot;'</note>
</trans-unit>
<trans-unit datatype="html" id="7894972847287473517">
<source>&quot;<x equiv-text="i.name" id="PH"/>&quot;</source>
<target>&quot;<x equiv-text="i.name" id="PH"/>&quot;</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">116</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="760986369763309193">
<source>, </source>
<target>, </target>
@@ -1582,14 +1470,6 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e">
<source>Suggestions:</source>
<target>Suggestions : </target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="27d158b47717ff9305d19866960418c603f19d55">
<source>Save current view</source>
<target>Enregistrer la vue actuelle</target>
@@ -1630,60 +1510,28 @@
<context context-type="linenumber">4</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6443586946875325554">
<source>Processing: <x equiv-text="countUploadingAndProcessing" id="PH"/></source>
<target>Traitement : <x equiv-text="countUploadingAndProcessing" id="PH"/></target>
<trans-unit datatype="html" id="8705589528094706681">
<source>The document has been uploaded and will be processed by the consumer shortly.</source>
<target>Le document a été transmis et sera traité sous peu.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">32</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="9182918211699394982">
<source>Failed: <x equiv-text="countFailed" id="PH"/></source>
<target>Échec : <x equiv-text="countFailed" id="PH"/></target>
<trans-unit datatype="html" id="4956689020592747108">
<source>There was an error while uploading the document: <x equiv-text="error.error.document" id="PH"/></source>
<target>Une erreur s'est produite lors du téléchargement du document : <x equiv-text="error.error.document" id="PH"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">71</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="534116346205124059">
<source>Added: <x equiv-text="countSuccess" id="PH"/></source>
<target>Ajout : <x equiv-text="countSuccess" id="PH"/></target>
<trans-unit datatype="html" id="7554858521017940575">
<source>An error has occurred while uploading the document. Sorry!</source>
<target>Une erreur s'est produite lors du téléchargement du document. Désolé !</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">38</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3852289441366561594">
<source>Connecting...</source>
<target>Connexion...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">118</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1245343823699368872">
<source>Uploading...</source>
<target>Téléchargement...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7446520539098045935">
<source>Upload complete, waiting...</source>
<target>Chargement terminé, en attente...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1405142710727603568">
<source>HTTP error: <x equiv-text="error.status" id="PH"/> <x equiv-text="error.statusText" id="PH_1"/></source>
<target>Erreur HTTP : <x equiv-text="error.status" id="PH"/> <x equiv-text="error.statusText" id="PH_1"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">136</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e022072b3e4dd77e3f09960817ef3359a49963b3">
@@ -1699,43 +1547,25 @@
<target>Déposer des documents ici ou</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">13</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="865c511f4a24558ed0e954f9bbbff557bbb8954d">
<source>Browse files</source>
<target>Parcourir les fichiers</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="33c76d75ce25ce3b05ab22877f1b6b09dcf603ae">
<source>{VAR_PLURAL, plural, =1 {Uploading file...} =other {Uploading <x id="INTERPOLATION"/> files...}}</source>
<target>{VAR_PLURAL, plural, =1 {Chargement du fichier...} =other {Chargement des <x id="INTERPOLATION"/> fichiers...}}</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="bd4a8607e4a002d939cffb347ec056664dfb2c73">
<source>Dismiss completed</source>
<target>Masquer lorsque terminé</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<note from="description" priority="1">This button dismisses all status messages about processed documents on the dashboard (failed and successful)</note>
</trans-unit>
<trans-unit datatype="html" id="90917e1a0a7bb59e9d11bdde9183e9391963e17b">
<source>{VAR_PLURAL, plural, =1 {One more document} other {<x id="INTERPOLATION"/> more documents}}</source>
<target>{VAR_PLURAL, plural, =1 {Un document supplémentaire} other {<x id="INTERPOLATION"/> documents supplémentaires}}</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">25</context>
</context-group>
<note from="description" priority="1">This is shown as a summary line when there are more than 5 document in the processing pipeline.</note>
</trans-unit>
<trans-unit datatype="html" id="710254a196a2649674438edf8a15b7ab1f48271b">
<source>Open document</source>
<target>Ouvrir le document</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">45</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="45854ddec74086b271e62be6a363f4fa5036f7fc">
<source>First steps</source>
<target>Premiers pas</target>
@@ -1845,7 +1675,7 @@
<target>Anglais (US)</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">82</context>
<context context-type="linenumber">74</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1858110241312746425">
@@ -1853,7 +1683,7 @@
<target>Allemand</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">83</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3071065188816255493">
@@ -1861,7 +1691,7 @@
<target>Néerlandais</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">84</context>
<context context-type="linenumber">76</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7633754075223722162">
@@ -1869,115 +1699,7 @@
<target>Français</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">85</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2119857572761283468">
<source>Document already exists.</source>
<target>Le document existe déjà.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">15</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="148389968432135849">
<source>File not found.</source>
<target>Fichier non trouvé.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1520671543092565667">
<source>Pre-consume script does not exist.</source>
<target>Le script de pré-traitement n'existe pas.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">17</context>
</context-group>
<note from="description" priority="1">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="7742915911032564889">
<source>Error while executing pre-consume script.</source>
<target>Erreur lors de l'exécution du script de pré-traitement.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">18</context>
</context-group>
<note from="description" priority="1">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="8995193730018060346">
<source>Post-consume script does not exist.</source>
<target>Le script de post-traitement n'existe pas.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">19</context>
</context-group>
<note from="description" priority="1">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="256773668518189604">
<source>Error while executing post-consume script.</source>
<target>Erreur lors de l'exécution du script de post-traitement.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">20</context>
</context-group>
<note from="description" priority="1">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="6252258095055634191">
<source>Received new file.</source>
<target>Réception d'un nouveau fichier.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7337565919209746135">
<source>File type not supported.</source>
<target>Type de fichier non pris en charge.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5002399167376099234">
<source>Processing document...</source>
<target>Traitement du document...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1085975194762600381">
<source>Generating thumbnail...</source>
<target>Génération de vignette...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3280851677698431426">
<source>Retrieving date from document...</source>
<target>Extraction de la date du document...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7162102384876037296">
<source>Saving document...</source>
<target>Enregistrement du document...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="4550450765009165976">
<source>Finished.</source>
<target>Terminé.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">27</context>
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1519954996184640001">
@@ -1985,7 +1707,7 @@
<target>Erreur</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5037437391296624618">
@@ -1993,7 +1715,7 @@
<target>Information</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">39</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7517688192215738656">
@@ -2001,7 +1723,7 @@
<target>NSA</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">17</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2691296884221415710">
@@ -2009,7 +1731,7 @@
<target>Correspondant</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">18</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5701618810648052610">
@@ -2017,7 +1739,7 @@
<target>Titre</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">19</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5066119607229701477">
@@ -2025,7 +1747,7 @@
<target>Type de document</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">20</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="4207916966377787111">
@@ -2033,7 +1755,7 @@
<target>Date de création</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">21</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="231679111972850796">
@@ -2041,7 +1763,7 @@
<target>Date d'ajout</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">22</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3553216189604488439">
@@ -2049,7 +1771,7 @@
<target>Date de modification</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">23</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2056433880533904076">
+71 -349
View File
@@ -1,60 +1,12 @@
<?xml version="1.0" ?><xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file datatype="plaintext" original="ng2.template" source-language="en-US" target-language="nl-NL">
<body>
<trans-unit datatype="html" id="9103526311244275943">
<source>Document added</source>
<target>Document toegevoegd</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="9204248378636247318">
<source>Document <x equiv-text="status.filename" id="PH"/> was added to paperless.</source>
<target>Document <x equiv-text="status.filename" id="PH"/> werd toegevoegd aan paperless.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1931214133925051574">
<source>Open document</source>
<target>Open document</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8582620835547864448">
<source>Could not add <x equiv-text="status.filename" id="PH"/>: <x equiv-text="status.message" id="PH_1"/></source>
<target>Kon niet toevoegen <x equiv-text="status.filename" id="PH"/>: <x equiv-text="status.message" id="PH_1"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">59</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1710712016675379662">
<source>New document detected</source>
<target>Nieuw document gedetecteerd</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="587031278561344416">
<source>Document <x equiv-text="status.filename" id="PH"/> is being processed by paperless.</source>
<target>Document <x equiv-text="status.filename" id="PH"/> wordt verwerkt door paperless.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/app.component.ts</context>
<context context-type="linenumber">65</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="4733307402565258070">
<source>Documents</source>
<target>Documenten</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">49</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2155249406916744630">
@@ -62,7 +14,7 @@
<target>View &quot;<x equiv-text="this.list.savedView.name" id="PH"/>&quot; met succes opgeslagen.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">109</context>
<context context-type="linenumber">94</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6837554170707123455">
@@ -70,7 +22,7 @@
<target>View &quot;<x equiv-text="savedView.name" id="PH"/>&quot; met succes gemaakt.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">130</context>
<context context-type="linenumber">115</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="9ca82952a6bc860b5391d5975322d8af8ceddfa4">
@@ -214,7 +166,7 @@
<target>Bevestig het verwijderen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">203</context>
<context context-type="linenumber">192</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5382975254277698192">
@@ -222,7 +174,7 @@
<target>Wilt u het document echt verwijderen &quot;<x equiv-text="this.document.title" id="PH"/>&quot;?</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">204</context>
<context context-type="linenumber">193</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6691075929777935948">
@@ -230,7 +182,7 @@
<target>De bestanden voor dit document worden definitief verwijderd. Deze bewerking kan niet ongedaan worden gemaakt.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">205</context>
<context context-type="linenumber">194</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="719892092227206532">
@@ -238,7 +190,7 @@
<target>Verwijder document</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">207</context>
<context context-type="linenumber">196</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1844801255494293730">
@@ -246,7 +198,7 @@
<target>Fout bij het verwijderen van het document: <x equiv-text="JSON.stringify(error)" id="PH"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">214</context>
<context context-type="linenumber">203</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="826b25211922a1b46436589233cb6f1a163d89b7">
@@ -598,7 +550,7 @@
<target>Opgeslagen view &quot;<x equiv-text="savedView.name" id="PH"/>&quot; verwijderd.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">67</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5647210819299459618">
@@ -606,7 +558,7 @@
<target>Instellingen succesvol opgeslagen.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">87</context>
<context context-type="linenumber">79</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6839066544204061364">
@@ -614,7 +566,7 @@
<target>Gebruik de systeemtaal</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">91</context>
<context context-type="linenumber">83</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7729897675462249787">
@@ -622,7 +574,7 @@
<target>Datumopmaak van weergavetaal gebruiken</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">95</context>
<context context-type="linenumber">87</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8488620293789898901">
@@ -630,7 +582,7 @@
<target>Fout bij het opslaan van de instellingen: <x equiv-text="JSON.stringify(error.error)" id="PH"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">111</context>
<context context-type="linenumber">103</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="121cc5391cd2a5115bc2b3160379ee5b36cd7716">
@@ -649,20 +601,12 @@
<context context-type="linenumber">10</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab">
<source>Notifications</source>
<target>Meldingen</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">115</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="99dee94e92dbd9e21a008d4569f9719ed206ae37">
<source>Saved views</source>
<target>Opgeslagen views</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">133</context>
<context context-type="linenumber">114</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="bbe41ac2ea4a6c00ea941a41b33105048f8e9f13">
@@ -817,60 +761,12 @@
<context context-type="linenumber">107</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8680abbea249ebe9c2fe35556559c8e1a9eb5841">
<source>Document processing</source>
<target>Verwerking van documenten</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">118</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2ad4d76b36341c589d94004ad2a213fd4d6f5ca0">
<source>Show notifications when new documents are detected</source>
<target>Toon meldingen wanneer nieuwe documenten worden gedetecteerd</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">122</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e775f4f7c40249d31426ae61a21616a0c9d8e84f">
<source>Show notifications when document processing completes successfully</source>
<target>Toon een melding wanneer documenten met succes zijn verwerkt</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e3844dd174d8e817ddb551fae28f14ae80ca36b6">
<source>Show notifications when document processing fails</source>
<target>Toon meldingen wanneer het verwerken van documenten faalt</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">124</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="af113f7c9f7e13145c3461f61a1aedf12d57bd71">
<source>Suppress notifications on dashboard</source>
<target>Onderdruk meldingen op het dashboard</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e27bd3804d2936a6897e81c2e52e294490e5e5a8">
<source>This will suppress all messages about document processing status on the dashboard.</source>
<target>Dit verbergt alle statusberichten op het dashboard die met het verwerken van documenten te maken hebben.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8cb90334f5dfd7fc67205085f59381e2a334ccfc">
<source>Appears on</source>
<target>Komt voor bij</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">145</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6717cf1acf04728fc2b7c39f6d3297f8ff15fde5">
@@ -878,7 +774,7 @@
<target>Toon op het dashboard</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">148</context>
<context context-type="linenumber">129</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="541bfc5b123b3f8867fd681eaceefb663a811973">
@@ -886,7 +782,7 @@
<target>Toon in de zijbalk</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">152</context>
<context context-type="linenumber">133</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="abba764a7a595d04dc8c3b26e04b3780d4fdb540">
@@ -894,7 +790,7 @@
<target>Geen opgeslagen views gedefinieerd.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">143</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="ef60a738a565f498b858e903e42bc5ffc3cc1299">
@@ -1019,7 +915,7 @@
</trans-unit>
<trans-unit datatype="html" id="5e2f1a4ea12a1b8606ee3f0548d0ba64bf266077">
<source>Inbox tags are automatically assigned to all consumed documents.</source>
<target>&quot;Postvak in&quot;-etiketten worden automatisch toegewezen aan alle verwerkte documenten.&quot;</target>
<target>&quot;Postvak in&quot;-etiketten worden automatisch toegewezen aan alle verwerkte documenten.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component.html</context>
<context context-type="linenumber">21</context>
@@ -1119,7 +1015,7 @@
<target>Beheren</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">107</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="408cb6073e60c5d966296a3207fc596adca75e01">
@@ -1127,15 +1023,15 @@
<target>Beheer</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">149</context>
<context context-type="linenumber">154</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="321e4419a943044e674beb55b8039f42a9761ca5">
<source>Info</source>
<target>Informatie</target>
<trans-unit datatype="html" id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
<source>Misc</source>
<target>Andere</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">155</context>
<context context-type="linenumber">160</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="fcfd4675b4c90f08d18d3abede9a9a4dff4cfdc7">
@@ -1143,7 +1039,7 @@
<target>Handleiding</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">162</context>
<context context-type="linenumber">167</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="355a222236bc01b9a8cd3cb9ecf76891125aed69">
@@ -1151,15 +1047,7 @@
<target>GitHub</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">170</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="ea3a452c5238897cabc5781308cceb2d37dcf258">
<source>Suggest an idea</source>
<target>Ideeënbus</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">176</context>
<context context-type="linenumber">174</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="af665f8de8fabe306aaf27443957e69bcbbce63c">
@@ -1175,7 +1063,7 @@
<target>Open documenten</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">87</context>
<context context-type="linenumber">92</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="dca5bf9344a759fa5a07f1b21f50286ec242ba44">
@@ -1183,7 +1071,7 @@
<target>Alles sluiten</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
<context context-type="linenumber">101</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5195932016807797291">
@@ -1395,14 +1283,6 @@
<context context-type="linenumber">73</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7894972847287473517">
<source>&quot;<x equiv-text="items[0].name" id="PH"/>&quot;</source>
<target>&quot;<x equiv-text="items[0].name" id="PH"/>&quot;</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="8639884465898458690">
<source>&quot;<x equiv-text="items[0].name" id="PH"/>&quot; and &quot;<x equiv-text="items[1].name" id="PH_1"/>&quot;</source>
<target>&quot;<x equiv-text="items[0].name" id="PH"/>&quot; en &quot;<x equiv-text="items[1].name" id="PH_1"/>&quot;</target>
@@ -1412,6 +1292,14 @@
</context-group>
<note from="description" priority="1">This is for messages like 'modify &quot;tag1&quot; and &quot;tag2&quot;'</note>
</trans-unit>
<trans-unit datatype="html" id="7894972847287473517">
<source>&quot;<x equiv-text="i.name" id="PH"/>&quot;</source>
<target>&quot;<x equiv-text="i.name" id="PH"/>&quot;</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">116</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="760986369763309193">
<source>, </source>
<target>, </target>
@@ -1582,14 +1470,6 @@
<context context-type="linenumber">27</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e">
<source>Suggestions:</source>
<target>Suggesties:</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="27d158b47717ff9305d19866960418c603f19d55">
<source>Save current view</source>
<target>Huidige view opslaan</target>
@@ -1630,60 +1510,28 @@
<context context-type="linenumber">4</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="6443586946875325554">
<source>Processing: <x equiv-text="countUploadingAndProcessing" id="PH"/></source>
<target>Bezig met verwerken: <x equiv-text="countUploadingAndProcessing" id="PH"/></target>
<trans-unit datatype="html" id="8705589528094706681">
<source>The document has been uploaded and will be processed by the consumer shortly.</source>
<target>Het document werd opgeladen en zal binnenkort worden verwerkt.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">32</context>
<context context-type="linenumber">63</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="9182918211699394982">
<source>Failed: <x equiv-text="countFailed" id="PH"/></source>
<target>Gefaald: <x equiv-text="countFailed" id="PH"/></target>
<trans-unit datatype="html" id="4956689020592747108">
<source>There was an error while uploading the document: <x equiv-text="error.error.document" id="PH"/></source>
<target>Fout bij het opladen van het document: <x equiv-text="error.error.document" id="PH"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">71</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="534116346205124059">
<source>Added: <x equiv-text="countSuccess" id="PH"/></source>
<target>Toegevoegd: <x equiv-text="countSuccess" id="PH"/></target>
<trans-unit datatype="html" id="7554858521017940575">
<source>An error has occurred while uploading the document. Sorry!</source>
<target>Er ging iets mis bij het opladen van het document. Onze excuses!</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">38</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3852289441366561594">
<source>Connecting...</source>
<target>Bezig met verbinden...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">118</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1245343823699368872">
<source>Uploading...</source>
<target>Bezig met opladen...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7446520539098045935">
<source>Upload complete, waiting...</source>
<target>Upload voltooid, klaar voor meer...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">126</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1405142710727603568">
<source>HTTP error: <x equiv-text="error.status" id="PH"/> <x equiv-text="error.statusText" id="PH_1"/></source>
<target>HTTP fout: <x equiv-text="error.status" id="PH"/> <x equiv-text="error.statusText" id="PH_1"/></target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">136</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="e022072b3e4dd77e3f09960817ef3359a49963b3">
@@ -1699,43 +1547,25 @@
<target>Sleep documenten hierheen, of</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">13</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="865c511f4a24558ed0e954f9bbbff557bbb8954d">
<source>Browse files</source>
<target>Kies bestanden</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">5</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="33c76d75ce25ce3b05ab22877f1b6b09dcf603ae">
<source>{VAR_PLURAL, plural, =1 {Uploading file...} =other {Uploading <x id="INTERPOLATION"/> files...}}</source>
<target>{VAR_PLURAL, plural, =1 {Bezig met opladen van het bestand...} =other {Bezig met opladen van <x id="INTERPOLATION"/> bestanden...}}</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="bd4a8607e4a002d939cffb347ec056664dfb2c73">
<source>Dismiss completed</source>
<target>Verberg verwerkte documenten</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
<note from="description" priority="1">This button dismisses all status messages about processed documents on the dashboard (failed and successful)</note>
</trans-unit>
<trans-unit datatype="html" id="90917e1a0a7bb59e9d11bdde9183e9391963e17b">
<source>{VAR_PLURAL, plural, =1 {One more document} other {<x id="INTERPOLATION"/> more documents}}</source>
<target>{VAR_PLURAL, plural, =1 {Nog één document} other {Nog <x id="INTERPOLATION"/> documenten}}</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">25</context>
</context-group>
<note from="description" priority="1">This is shown as a summary line when there are more than 5 document in the processing pipeline.</note>
</trans-unit>
<trans-unit datatype="html" id="710254a196a2649674438edf8a15b7ab1f48271b">
<source>Open document</source>
<target>Open document</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
<context context-type="linenumber">45</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="45854ddec74086b271e62be6a363f4fa5036f7fc">
<source>First steps</source>
<target>Laten we aan de slag gaan</target>
@@ -1845,7 +1675,7 @@
<target>Engels (US)</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">82</context>
<context context-type="linenumber">74</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1858110241312746425">
@@ -1853,7 +1683,7 @@
<target>Duits</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">83</context>
<context context-type="linenumber">75</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3071065188816255493">
@@ -1861,7 +1691,7 @@
<target>Nederlands</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">84</context>
<context context-type="linenumber">76</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7633754075223722162">
@@ -1869,115 +1699,7 @@
<target>Frans</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">85</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2119857572761283468">
<source>Document already exists.</source>
<target>Document bestaat al.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">15</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="148389968432135849">
<source>File not found.</source>
<target>Bestand niet gevonden.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1520671543092565667">
<source>Pre-consume script does not exist.</source>
<target>Pre-verwerkingsscript bestaat niet.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">17</context>
</context-group>
<note from="description" priority="1">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="7742915911032564889">
<source>Error while executing pre-consume script.</source>
<target>Fout tijdens het uitvoeren van het pre-verwerkingsscript</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">18</context>
</context-group>
<note from="description" priority="1">Pre-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="8995193730018060346">
<source>Post-consume script does not exist.</source>
<target>Post-verwerkingsscript bestaat niet.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">19</context>
</context-group>
<note from="description" priority="1">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="256773668518189604">
<source>Error while executing post-consume script.</source>
<target>Fout tijdens het uitvoeren van het post-verwerkingsscript</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">20</context>
</context-group>
<note from="description" priority="1">Post-Consume is a term that appears like that in the documentation as well and does not need a specific translation</note>
</trans-unit>
<trans-unit datatype="html" id="6252258095055634191">
<source>Received new file.</source>
<target>Nieuw bestand ontvangen.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7337565919209746135">
<source>File type not supported.</source>
<target>Bestandstype niet ondersteund.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5002399167376099234">
<source>Processing document...</source>
<target>Document wordt verwerkt...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1085975194762600381">
<source>Generating thumbnail...</source>
<target>Voorbeeldweergave wordt gemaakt...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3280851677698431426">
<source>Retrieving date from document...</source>
<target>Datum wordt gezocht in document...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7162102384876037296">
<source>Saving document...</source>
<target>Document wordt opgeslagen...</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="4550450765009165976">
<source>Finished.</source>
<target>Klaar.</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/consumer-status.service.ts</context>
<context context-type="linenumber">27</context>
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="1519954996184640001">
@@ -1985,7 +1707,7 @@
<target>Fout</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">31</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5037437391296624618">
@@ -1993,7 +1715,7 @@
<target>Informatie</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">39</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="7517688192215738656">
@@ -2001,7 +1723,7 @@
<target>ASN</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">17</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2691296884221415710">
@@ -2009,7 +1731,7 @@
<target>Correspondent</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">18</context>
<context context-type="linenumber">17</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5701618810648052610">
@@ -2017,7 +1739,7 @@
<target>Titel</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">19</context>
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="5066119607229701477">
@@ -2025,7 +1747,7 @@
<target>Documenttype</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">20</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="4207916966377787111">
@@ -2033,7 +1755,7 @@
<target>Aangemaakt</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">21</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="231679111972850796">
@@ -2041,7 +1763,7 @@
<target>Toegevoegd</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">22</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="3553216189604488439">
@@ -2049,7 +1771,7 @@
<target>Gewijzigd</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">23</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit datatype="html" id="2056433880533904076">
-4
View File
@@ -111,7 +111,3 @@ body {
font-size: 16px;
}
}
.ngx-file-drop__drop-zone--over {
background-color: $primaryFaded !important;
}
-14
View File
@@ -352,20 +352,6 @@ $border-color-dark-mode: #47494f;
.bg-dark {
background-color: $bg-light-dark-mode !important;
}
.ngx-file-drop__drop-zone--over {
background-color: darken($primary-dark-mode, 35%) !important;
}
.alert-secondary {
background-color: $bg-light-dark-mode;
border-color: darken($bg-light-dark-mode, 10%);
color: $text-color-dark-mode-accent;
}
.progress-bar.bg-primary {
background-color: darken($primary-dark-mode, 5%) !important;
}
}
body.color-scheme-dark {
BIN
View File
Binary file not shown.
+45 -13
View File
@@ -50,31 +50,26 @@ class DocumentAdmin(admin.ModelAdmin):
"modified",
"mime_type",
"storage_type",
"filename",
"checksum",
"archive_filename",
"archive_checksum"
)
"filename")
list_display_links = ("title",)
list_display = (
"id",
"correspondent",
"title",
"mime_type",
"filename",
"archive_filename"
"tags_",
"created",
)
list_filter = (
("mime_type"),
("archive_serial_number", admin.EmptyFieldListFilter),
("archive_filename", admin.EmptyFieldListFilter),
"document_type",
"tags",
"correspondent"
)
filter_horizontal = ("tags",)
ordering = ["-id"]
ordering = ["-created"]
date_hierarchy = "created"
@@ -100,6 +95,42 @@ class DocumentAdmin(admin.ModelAdmin):
index.add_or_update_document(obj)
super(DocumentAdmin, self).save_model(request, obj, form, change)
@mark_safe
def tags_(self, obj):
r = ""
for tag in obj.tags.all():
r += self._html_tag(
"span",
tag.name + ", "
)
return r
@staticmethod
def _html_tag(kind, inside=None, **kwargs):
attributes = format_html_join(' ', '{}="{}"', kwargs.items())
if inside is not None:
return format_html("<{kind} {attributes}>{inside}</{kind}>",
kind=kind, attributes=attributes, inside=inside)
return format_html("<{} {}/>", kind, attributes)
class LogAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
list_display = ("created", "message", "level",)
list_filter = ("level", "created",)
ordering = ('-created',)
list_display_links = ("created", "message")
class RuleInline(admin.TabularInline):
model = SavedViewFilterRule
@@ -118,4 +149,5 @@ admin.site.register(Correspondent, CorrespondentAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(DocumentType, DocumentTypeAdmin)
admin.site.register(Document, DocumentAdmin)
admin.site.register(Log, LogAdmin)
admin.site.register(SavedView, SavedViewAdmin)
+39 -66
View File
@@ -5,7 +5,10 @@ import pickle
import re
from django.conf import settings
from django.core.cache import cache
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import MultiLabelBinarizer, LabelBinarizer
from sklearn.utils.multiclass import type_of_target
from documents.models import Document, MatchingModel
@@ -14,7 +17,7 @@ class IncompatibleClassifierVersionError(Exception):
pass
logger = logging.getLogger("paperless.classifier")
logger = logging.getLogger(__name__)
def preprocess_content(content):
@@ -23,46 +26,15 @@ def preprocess_content(content):
return content
def load_classifier():
if not os.path.isfile(settings.MODEL_FILE):
logger.debug(
f"Document classification model does not exist (yet), not "
f"performing automatic matching."
)
return None
version = os.stat(settings.MODEL_FILE).st_mtime
classifier = cache.get("paperless-classifier", version=version)
if not classifier:
classifier = DocumentClassifier()
try:
classifier.load()
cache.set("paperless-classifier", classifier,
version=version, timeout=86400)
except (EOFError, IncompatibleClassifierVersionError) as e:
# there's something wrong with the model file.
logger.exception(
f"Unrecoverable error while loading document "
f"classification model, deleting model file."
)
os.unlink(settings.MODEL_FILE)
classifier = None
except OSError as e:
logger.error(
f"Error while loading document classification model: {str(e)}"
)
classifier = None
return classifier
class DocumentClassifier(object):
FORMAT_VERSION = 6
def __init__(self):
# mtime of the model file on disk. used to prevent reloading when
# nothing has changed.
self.classifier_version = 0
# hash of the training data. used to prevent re-training when the
# training data has not changed.
self.data_hash = None
@@ -73,23 +45,30 @@ class DocumentClassifier(object):
self.correspondent_classifier = None
self.document_type_classifier = None
def load(self):
with open(settings.MODEL_FILE, "rb") as f:
schema_version = pickle.load(f)
def reload(self):
if os.path.getmtime(settings.MODEL_FILE) > self.classifier_version:
with open(settings.MODEL_FILE, "rb") as f:
schema_version = pickle.load(f)
if schema_version != self.FORMAT_VERSION:
raise IncompatibleClassifierVersionError(
"Cannor load classifier, incompatible versions.")
else:
self.data_hash = pickle.load(f)
self.data_vectorizer = pickle.load(f)
self.tags_binarizer = pickle.load(f)
if schema_version != self.FORMAT_VERSION:
raise IncompatibleClassifierVersionError(
"Cannor load classifier, incompatible versions.")
else:
if self.classifier_version > 0:
# Don't be confused by this check. It's simply here
# so that we wont log anything on initial reload.
logger.info("Classifier updated on disk, "
"reloading classifier models")
self.data_hash = pickle.load(f)
self.data_vectorizer = pickle.load(f)
self.tags_binarizer = pickle.load(f)
self.tags_classifier = pickle.load(f)
self.correspondent_classifier = pickle.load(f)
self.document_type_classifier = pickle.load(f)
self.tags_classifier = pickle.load(f)
self.correspondent_classifier = pickle.load(f)
self.document_type_classifier = pickle.load(f)
self.classifier_version = os.path.getmtime(settings.MODEL_FILE)
def save(self):
def save_classifier(self):
with open(settings.MODEL_FILE, "wb") as f:
pickle.dump(self.FORMAT_VERSION, f)
pickle.dump(self.data_hash, f)
@@ -102,17 +81,13 @@ class DocumentClassifier(object):
pickle.dump(self.document_type_classifier, f)
def train(self):
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import MultiLabelBinarizer, LabelBinarizer
data = list()
labels_tags = list()
labels_correspondent = list()
labels_document_type = list()
# Step 1: Extract and preprocess training data from the database.
logger.debug("Gathering data from database...")
logging.getLogger(__name__).debug("Gathering data from database...")
m = hashlib.sha1()
for doc in Document.objects.order_by('pk').exclude(tags__is_inbox_tag=True): # NOQA: E501
preprocessed_content = preprocess_content(doc.content)
@@ -159,7 +134,7 @@ class DocumentClassifier(object):
num_correspondents = len(set(labels_correspondent) | {-1}) - 1
num_document_types = len(set(labels_document_type) | {-1}) - 1
logger.debug(
logging.getLogger(__name__).debug(
"{} documents, {} tag(s), {} correspondent(s), "
"{} document type(s).".format(
len(data),
@@ -170,7 +145,7 @@ class DocumentClassifier(object):
)
# Step 2: vectorize data
logger.debug("Vectorizing data...")
logging.getLogger(__name__).debug("Vectorizing data...")
self.data_vectorizer = CountVectorizer(
analyzer="word",
ngram_range=(1, 2),
@@ -180,7 +155,7 @@ class DocumentClassifier(object):
# Step 3: train the classifiers
if num_tags > 0:
logger.debug("Training tags classifier...")
logging.getLogger(__name__).debug("Training tags classifier...")
if num_tags == 1:
# Special case where only one tag has auto:
@@ -199,12 +174,12 @@ class DocumentClassifier(object):
self.tags_classifier.fit(data_vectorized, labels_tags_vectorized)
else:
self.tags_classifier = None
logger.debug(
logging.getLogger(__name__).debug(
"There are no tags. Not training tags classifier."
)
if num_correspondents > 0:
logger.debug(
logging.getLogger(__name__).debug(
"Training correspondent classifier..."
)
self.correspondent_classifier = MLPClassifier(tol=0.01)
@@ -214,13 +189,13 @@ class DocumentClassifier(object):
)
else:
self.correspondent_classifier = None
logger.debug(
logging.getLogger(__name__).debug(
"There are no correspondents. Not training correspondent "
"classifier."
)
if num_document_types > 0:
logger.debug(
logging.getLogger(__name__).debug(
"Training document type classifier..."
)
self.document_type_classifier = MLPClassifier(tol=0.01)
@@ -230,7 +205,7 @@ class DocumentClassifier(object):
)
else:
self.document_type_classifier = None
logger.debug(
logging.getLogger(__name__).debug(
"There are no document types. Not training document type "
"classifier."
)
@@ -262,8 +237,6 @@ class DocumentClassifier(object):
return None
def predict_tags(self, content):
from sklearn.utils.multiclass import type_of_target
if self.tags_classifier:
X = self.data_vectorizer.transform([preprocess_content(content)])
y = self.tags_classifier.predict(X)
+43 -104
View File
@@ -1,12 +1,9 @@
import datetime
import hashlib
import os
import uuid
from subprocess import Popen
import magic
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.conf import settings
from django.db import transaction
from django.db.models import Q
@@ -14,7 +11,7 @@ from django.utils import timezone
from filelock import FileLock
from rest_framework.reverse import reverse
from .classifier import load_classifier
from .classifier import DocumentClassifier, IncompatibleClassifierVersionError
from .file_handling import create_source_path_directory, \
generate_unique_filename
from .loggers import LoggingMixin
@@ -30,45 +27,8 @@ class ConsumerError(Exception):
pass
MESSAGE_DOCUMENT_ALREADY_EXISTS = "document_already_exists"
MESSAGE_FILE_NOT_FOUND = "file_not_found"
MESSAGE_PRE_CONSUME_SCRIPT_NOT_FOUND = "pre_consume_script_not_found"
MESSAGE_PRE_CONSUME_SCRIPT_ERROR = "pre_consume_script_error"
MESSAGE_POST_CONSUME_SCRIPT_NOT_FOUND = "post_consume_script_not_found"
MESSAGE_POST_CONSUME_SCRIPT_ERROR = "post_consume_script_error"
MESSAGE_NEW_FILE = "new_file"
MESSAGE_UNSUPPORTED_TYPE = "unsupported_type"
MESSAGE_PARSING_DOCUMENT = "parsing_document"
MESSAGE_GENERATING_THUMBNAIL = "generating_thumbnail"
MESSAGE_PARSE_DATE = "parse_date"
MESSAGE_SAVE_DOCUMENT = "save_document"
MESSAGE_FINISHED = "finished"
class Consumer(LoggingMixin):
logging_name = "paperless.consumer"
def _send_progress(self, current_progress, max_progress, status,
message=None, document_id=None):
payload = {
'filename': os.path.basename(self.filename) if self.filename else None, # NOQA: E501
'task_id': self.task_id,
'current_progress': current_progress,
'max_progress': max_progress,
'status': status,
'message': message,
'document_id': document_id
}
async_to_sync(self.channel_layer.group_send)("status_updates",
{'type': 'status_update',
'data': payload})
def _fail(self, message, log_message=None):
self._send_progress(100, 100, 'FAILED', message)
self.log("error", log_message or message)
raise ConsumerError(f"{self.filename}: {log_message or message}")
def __init__(self):
super().__init__()
self.path = None
@@ -77,16 +37,15 @@ class Consumer(LoggingMixin):
self.override_correspondent_id = None
self.override_tag_ids = None
self.override_document_type_id = None
self.task_id = None
self.channel_layer = get_channel_layer()
def pre_check_file_exists(self):
if not os.path.isfile(self.path):
self._fail(
MESSAGE_FILE_NOT_FOUND,
f"Cannot consume {self.path}: File not found."
self.log(
"error",
"Cannot consume {}: It is not a file.".format(self.path)
)
raise ConsumerError("Cannot consume {}: It is not a file".format(
self.path))
def pre_check_duplicate(self):
with open(self.path, "rb") as f:
@@ -94,9 +53,12 @@ class Consumer(LoggingMixin):
if Document.objects.filter(Q(checksum=checksum) | Q(archive_checksum=checksum)).exists(): # NOQA: E501
if settings.CONSUMER_DELETE_DUPLICATES:
os.unlink(self.path)
self._fail(
MESSAGE_DOCUMENT_ALREADY_EXISTS,
f"Not consuming {self.filename}: It is a duplicate."
self.log(
"error",
"Not consuming {}: It is a duplicate.".format(self.filename)
)
raise ConsumerError(
"Not consuming {}: It is a duplicate.".format(self.filename)
)
def pre_check_directories(self):
@@ -110,16 +72,14 @@ class Consumer(LoggingMixin):
return
if not os.path.isfile(settings.PRE_CONSUME_SCRIPT):
self._fail(
MESSAGE_PRE_CONSUME_SCRIPT_NOT_FOUND,
raise ConsumerError(
f"Configured pre-consume script "
f"{settings.PRE_CONSUME_SCRIPT} does not exist.")
try:
Popen((settings.PRE_CONSUME_SCRIPT, self.path)).wait()
except Exception as e:
self._fail(
MESSAGE_PRE_CONSUME_SCRIPT_ERROR,
raise ConsumerError(
f"Error while executing pre-consume script: {e}"
)
@@ -128,11 +88,9 @@ class Consumer(LoggingMixin):
return
if not os.path.isfile(settings.POST_CONSUME_SCRIPT):
self._fail(
MESSAGE_POST_CONSUME_SCRIPT_NOT_FOUND,
raise ConsumerError(
f"Configured post-consume script "
f"{settings.POST_CONSUME_SCRIPT} does not exist."
)
f"{settings.POST_CONSUME_SCRIPT} does not exist.")
try:
Popen((
@@ -148,9 +106,8 @@ class Consumer(LoggingMixin):
"name", flat=True)))
)).wait()
except Exception as e:
self._fail(
MESSAGE_POST_CONSUME_SCRIPT_ERROR,
f"Error while executing post-consume script: {e}"
raise ConsumerError(
f"Error while executing pre-consume script: {e}"
)
def try_consume_file(self,
@@ -159,8 +116,7 @@ class Consumer(LoggingMixin):
override_title=None,
override_correspondent_id=None,
override_document_type_id=None,
override_tag_ids=None,
task_id=None):
override_tag_ids=None):
"""
Return the document object if it was successfully created.
"""
@@ -171,9 +127,6 @@ class Consumer(LoggingMixin):
self.override_correspondent_id = override_correspondent_id
self.override_document_type_id = override_document_type_id
self.override_tag_ids = override_tag_ids
self.task_id = task_id or str(uuid.uuid4())
self._send_progress(0, 100, 'STARTING', MESSAGE_NEW_FILE)
# this is for grouping logging entries for this particular file
# together.
@@ -196,10 +149,11 @@ class Consumer(LoggingMixin):
parser_class = get_parser_class_for_mime_type(mime_type)
if not parser_class:
self._fail(
MESSAGE_UNSUPPORTED_TYPE,
f"Unsupported mime type {mime_type}"
)
raise ConsumerError(
f"Unsupported mime type {mime_type} of file {self.filename}")
else:
self.log("debug",
f"Parser: {parser_class.__name__}")
# Notify all listeners that we're going to do some work.
@@ -211,52 +165,35 @@ class Consumer(LoggingMixin):
self.run_pre_consume_script()
def progress_callback(current_progress, max_progress):
# recalculate progress to be within 20 and 80
p = int((current_progress / max_progress) * 50 + 20)
self._send_progress(p, 100, "WORKING")
# This doesn't parse the document yet, but gives us a parser.
document_parser = parser_class(self.logging_group, progress_callback)
self.log("debug", f"Parser: {type(document_parser).__name__}")
document_parser = parser_class(self.logging_group)
# However, this already created working directories which we have to
# clean up.
# Parse the document. This may take some time.
text = None
date = None
thumbnail = None
archive_path = None
try:
self._send_progress(20, 100, 'WORKING', MESSAGE_PARSING_DOCUMENT)
self.log("debug", "Parsing {}...".format(self.filename))
document_parser.parse(self.path, mime_type, self.filename)
self.log("debug", f"Generating thumbnail for {self.filename}...")
self._send_progress(70, 100, 'WORKING',
MESSAGE_GENERATING_THUMBNAIL)
thumbnail = document_parser.get_optimised_thumbnail(
self.path, mime_type, self.filename)
self.path, mime_type)
text = document_parser.get_text()
date = document_parser.get_date()
if not date:
self._send_progress(90, 100, 'WORKING',
MESSAGE_PARSE_DATE)
date = parse_date(self.filename, text)
archive_path = document_parser.get_archive_path()
except ParseError as e:
document_parser.cleanup()
self._fail(
str(e),
f"Error while consuming document {self.filename}: {e}"
)
self.log(
"error",
f"Error while consuming document {self.filename}: {e}")
raise ConsumerError(e)
# Prepare the document classifier.
@@ -264,9 +201,15 @@ class Consumer(LoggingMixin):
# reloading the classifier multiple times, since there are multiple
# post-consume hooks that all require the classifier.
classifier = load_classifier()
try:
classifier = DocumentClassifier()
classifier.reload()
except (OSError, EOFError, IncompatibleClassifierVersionError) as e:
self.log(
"warning",
f"Cannot classify documents: {e}.")
classifier = None
self._send_progress(95, 100, 'WORKING', MESSAGE_SAVE_DOCUMENT)
# now that everything is done, we can start to store the document
# in the system. This will be a transaction and reasonably fast.
try:
@@ -292,7 +235,8 @@ class Consumer(LoggingMixin):
# After everything is in the database, copy the files into
# place. If this fails, we'll also rollback the transaction.
with FileLock(settings.MEDIA_LOCK):
document.filename = generate_unique_filename(document)
document.filename = generate_unique_filename(
document, settings.ORIGINALS_DIR)
create_source_path_directory(document.source_path)
self._write(document.storage_type,
@@ -302,10 +246,6 @@ class Consumer(LoggingMixin):
thumbnail, document.thumbnail_path)
if archive_path and os.path.isfile(archive_path):
document.archive_filename = generate_unique_filename(
document,
archive_filename=True
)
create_source_path_directory(document.archive_path)
self._write(document.storage_type,
archive_path, document.archive_path)
@@ -323,11 +263,12 @@ class Consumer(LoggingMixin):
os.unlink(self.path)
except Exception as e:
self._fail(
str(e),
self.log(
"error",
f"The following error occured while consuming "
f"{self.filename}: {e}"
)
raise ConsumerError(e)
finally:
document_parser.cleanup()
@@ -338,8 +279,6 @@ class Consumer(LoggingMixin):
"Document {} consumption finished".format(document)
)
self._send_progress(100, 100, 'SUCCESS', MESSAGE_FINISHED, document.id)
return document
def _store(self, text, date, mime_type):
+12 -47
View File
@@ -8,9 +8,6 @@ from django.conf import settings
from django.template.defaultfilters import slugify
logger = logging.getLogger("paperless.filehandling")
class defaultdictNoStr(defaultdict):
def __str__(self):
@@ -79,40 +76,12 @@ def many_to_dictionary(field):
return mydictionary
def generate_unique_filename(doc,
archive_filename=False):
"""
Generates a unique filename for doc in settings.ORIGINALS_DIR.
The returned filename is guaranteed to be either the current filename
of the document if unchanged, or a new filename that does not correspondent
to any existing files. The function will append _01, _02, etc to the
filename before the extension to avoid conflicts.
If archive_filename is True, return a unique archive filename instead.
"""
if archive_filename:
old_filename = doc.archive_filename
root = settings.ARCHIVE_DIR
else:
old_filename = doc.filename
root = settings.ORIGINALS_DIR
# If generating archive filenames, try to make a name that is similar to
# the original filename first.
if archive_filename and doc.filename:
new_filename = os.path.splitext(doc.filename)[0] + ".pdf"
if new_filename == old_filename or not os.path.exists(os.path.join(root, new_filename)): # NOQA: E501
return new_filename
def generate_unique_filename(doc, root):
counter = 0
while True:
new_filename = generate_filename(
doc, counter, archive_filename=archive_filename)
if new_filename == old_filename:
new_filename = generate_filename(doc, counter)
if new_filename == doc.filename:
# still the same as before.
return new_filename
@@ -122,7 +91,7 @@ def generate_unique_filename(doc,
return new_filename
def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False):
def generate_filename(doc, counter=0, append_gpg=True):
path = ""
try:
@@ -151,11 +120,6 @@ def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False):
else:
document_type = "none"
if doc.archive_serial_number:
asn = str(doc.archive_serial_number)
else:
asn = "none"
path = settings.PAPERLESS_FILENAME_FORMAT.format(
title=pathvalidate.sanitize_filename(
doc.title, replacement_text="-"),
@@ -169,7 +133,6 @@ def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False):
added_year=doc.added.year if doc.added else "none",
added_month=f"{doc.added.month:02}" if doc.added else "none",
added_day=f"{doc.added.day:02}" if doc.added else "none",
asn=asn,
tags=tags,
tag_list=tag_list
).strip()
@@ -177,21 +140,23 @@ def generate_filename(doc, counter=0, append_gpg=True, archive_filename=False):
path = path.strip(os.sep)
except (ValueError, KeyError, IndexError):
logger.warning(
logging.getLogger(__name__).warning(
f"Invalid PAPERLESS_FILENAME_FORMAT: "
f"{settings.PAPERLESS_FILENAME_FORMAT}, falling back to default")
counter_str = f"_{counter:02}" if counter else ""
filetype_str = ".pdf" if archive_filename else doc.file_type
if len(path) > 0:
filename = f"{path}{counter_str}{filetype_str}"
filename = f"{path}{counter_str}{doc.file_type}"
else:
filename = f"{doc.pk:07}{counter_str}{filetype_str}"
filename = f"{doc.pk:07}{counter_str}{doc.file_type}"
# Append .gpg for encrypted files
if append_gpg and doc.storage_type == doc.STORAGE_TYPE_GPG:
filename += ".gpg"
return filename
def archive_name_from_filename(filename):
return os.path.splitext(filename)[0] + ".pdf"
+3 -3
View File
@@ -12,7 +12,7 @@ from whoosh.qparser.dateparse import DateParserPlugin
from whoosh.writing import AsyncWriter
logger = logging.getLogger("paperless.index")
logger = logging.getLogger(__name__)
class JsonFormatter(Formatter):
@@ -78,8 +78,8 @@ def open_index(recreate=False):
try:
if exists_in(settings.INDEX_DIR) and not recreate:
return open_dir(settings.INDEX_DIR, schema=get_schema())
except Exception:
logger.exception(f"Error while opening the index, recreating.")
except Exception as e:
logger.error(f"Error while opening the index: {e}, recreating.")
if not os.path.isdir(settings.INDEX_DIR):
os.makedirs(settings.INDEX_DIR, exist_ok=True)
+19 -10
View File
@@ -4,24 +4,33 @@ import uuid
from django.conf import settings
class PaperlessHandler(logging.Handler):
def emit(self, record):
if settings.DISABLE_DBHANDLER:
return
# We have to do the import here or Django will barf when it tries to
# load this because the apps aren't loaded at that point
from .models import Log
kwargs = {"message": record.msg, "level": record.levelno}
if hasattr(record, "group"):
kwargs["group"] = record.group
Log.objects.create(**kwargs)
class LoggingMixin:
logging_group = None
logging_name = None
def renew_logging_group(self):
self.logging_group = uuid.uuid4()
def log(self, level, message, **kwargs):
if self.logging_name:
logger = logging.getLogger(self.logging_name)
else:
name = ".".join([
self.__class__.__module__,
self.__class__.__name__
])
logger = logging.getLogger(name)
target = ".".join([self.__class__.__module__, self.__class__.__name__])
logger = logging.getLogger(target)
getattr(logger, level)(message, extra={
"group": self.logging_group
@@ -16,12 +16,12 @@ from whoosh.writing import AsyncWriter
from documents.models import Document
from ... import index
from ...file_handling import create_source_path_directory, \
generate_unique_filename
from ...file_handling import create_source_path_directory
from ...mixins import Renderable
from ...parsers import get_parser_class_for_mime_type
logger = logging.getLogger("paperless.management.archiver")
logger = logging.getLogger(__name__)
def handle_document(document_id):
@@ -40,16 +40,13 @@ def handle_document(document_id):
with transaction.atomic():
with open(parser.get_archive_path(), 'rb') as f:
checksum = hashlib.md5(f.read()).hexdigest()
# I'm going to save first so that in case the file move
# i'm going to save first so that in case the file move
# fails, the database is rolled back.
# We also don't use save() since that triggers the filehandling
# we also don't use save() since that triggers the filehandling
# logic, and we don't want that yet (file not yet in place)
document.archive_filename = generate_unique_filename(
document, archive_filename=True)
Document.objects.filter(pk=document.pk).update(
archive_checksum=checksum,
content=parser.get_text(),
archive_filename=document.archive_filename
content=parser.get_text()
)
with FileLock(settings.MEDIA_LOCK):
create_source_path_directory(document.archive_path)
@@ -60,12 +57,12 @@ def handle_document(document_id):
index.update_document(writer, document)
except Exception as e:
logger.exception(f"Error while parsing document {document}")
logger.error(f"Error while parsing document {document}: {str(e)}")
finally:
parser.cleanup()
class Command(BaseCommand):
class Command(Renderable, BaseCommand):
help = """
Using the current classification model, assigns correspondents, tags
@@ -74,6 +71,10 @@ class Command(BaseCommand):
modified) after their initial import.
""".replace(" ", "")
def __init__(self, *args, **kwargs):
self.verbosity = 0
BaseCommand.__init__(self, *args, **kwargs)
def add_arguments(self, parser):
parser.add_argument(
"-f", "--overwrite",
@@ -105,7 +106,7 @@ class Command(BaseCommand):
document_ids = list(map(
lambda doc: doc.id,
filter(
lambda d: overwrite or not d.has_archive_version,
lambda d: overwrite or not d.archive_checksum,
documents
)
))
@@ -17,11 +17,11 @@ try:
except ImportError:
INotify = flags = None
logger = logging.getLogger("paperless.management.consumer")
logger = logging.getLogger(__name__)
def _tags_from_path(filepath):
"""Walk up the directory tree from filepath to CONSUMPTION_DIR
"""Walk up the directory tree from filepath to CONSUMPTION_DIr
and get or create Tag IDs for every directory.
"""
tag_ids = set()
@@ -54,7 +54,8 @@ def _consume(filepath):
if settings.CONSUMER_SUBDIRS_AS_TAGS:
tag_ids = _tags_from_path(filepath)
except Exception as e:
logger.exception("Error creating tags from path")
logger.error(
"Error creating tags from path: {}".format(e))
try:
async_task("documents.tasks.consume_file",
@@ -65,7 +66,8 @@ def _consume(filepath):
# Catch all so that the consumer won't crash.
# This is also what the test case is listening for to check for
# errors.
logger.exception("Error while consuming document")
logger.error(
"Error while consuming document: {}".format(e))
def _consume_wait_unmodified(file, num_tries=20, wait_time=1):
@@ -106,7 +108,12 @@ class Command(BaseCommand):
# This is here primarily for the tests and is irrelevant in production.
stop_flag = False
observer = None
def __init__(self, *args, **kwargs):
self.logger = logging.getLogger(__name__)
BaseCommand.__init__(self, *args, **kwargs)
self.observer = None
def add_arguments(self, parser):
parser.add_argument(
@@ -154,7 +161,7 @@ class Command(BaseCommand):
logger.debug("Consumer exiting.")
def handle_polling(self, directory, recursive):
logger.info(
logging.getLogger(__name__).info(
f"Polling directory for changes: {directory}")
self.observer = PollingObserver(timeout=settings.CONSUMER_POLLING)
self.observer.schedule(Handler(), directory, recursive=recursive)
@@ -169,7 +176,7 @@ class Command(BaseCommand):
self.observer.join()
def handle_inotify(self, directory, recursive):
logger.info(
logging.getLogger(__name__).info(
f"Using inotify to watch directory for changes: {directory}")
inotify = INotify()
@@ -1,9 +1,10 @@
from django.core.management.base import BaseCommand
from ...mixins import Renderable
from ...tasks import train_classifier
class Command(BaseCommand):
class Command(Renderable, BaseCommand):
help = """
Trains the classifier on your data and saves the resulting models to a
@@ -16,9 +16,10 @@ from documents.settings import EXPORTER_FILE_NAME, EXPORTER_THUMBNAIL_NAME, \
EXPORTER_ARCHIVE_NAME
from paperless.db import GnuPG
from ...file_handling import generate_filename, delete_empty_directories
from ...mixins import Renderable
class Command(BaseCommand):
class Command(Renderable, BaseCommand):
help = """
Decrypt and rename all files in our collection into a given target
@@ -139,7 +140,7 @@ class Command(BaseCommand):
thumbnail_target = os.path.join(self.target, thumbnail_name)
document_dict[EXPORTER_THUMBNAIL_NAME] = thumbnail_name
if document.has_archive_version:
if os.path.exists(document.archive_path):
archive_name = base_name + "-archive.pdf"
archive_target = os.path.join(self.target, archive_name)
document_dict[EXPORTER_ARCHIVE_NAME] = archive_name
@@ -15,6 +15,7 @@ from documents.models import Document
from documents.settings import EXPORTER_FILE_NAME, EXPORTER_THUMBNAIL_NAME, \
EXPORTER_ARCHIVE_NAME
from ...file_handling import create_source_path_directory
from ...mixins import Renderable
from ...signals.handlers import update_filename_and_move_files
@@ -27,7 +28,7 @@ def disable_signal(sig, receiver, sender):
sig.connect(receiver=receiver, sender=sender)
class Command(BaseCommand):
class Command(Renderable, BaseCommand):
help = """
Using a manifest.json file, load the data from there, and import the
@@ -151,9 +152,6 @@ class Command(BaseCommand):
shutil.copy2(thumbnail_path, document.thumbnail_path)
if archive_path:
create_source_path_directory(document.archive_path)
# TODO: this assumes that the export is valid and
# archive_filename is present on all documents with
# archived files
shutil.copy2(archive_path, document.archive_path)
document.save()
@@ -1,17 +1,24 @@
from django.core.management import BaseCommand
from django.db import transaction
from documents.mixins import Renderable
from documents.tasks import index_reindex, index_optimize
class Command(BaseCommand):
class Command(Renderable, BaseCommand):
help = "Manages the document index."
def __init__(self, *args, **kwargs):
self.verbosity = 0
BaseCommand.__init__(self, *args, **kwargs)
def add_arguments(self, parser):
parser.add_argument("command", choices=['reindex', 'optimize'])
def handle(self, *args, **options):
self.verbosity = options["verbosity"]
with transaction.atomic():
if options['command'] == 'reindex':
index_reindex()
@@ -0,0 +1,12 @@
from django.core.management.base import BaseCommand
from documents.models import Log
class Command(BaseCommand):
help = "A quick & dirty way to see what's in the logs"
def handle(self, *args, **options):
for log in Log.objects.order_by("pk"):
print(log)
@@ -5,16 +5,23 @@ from django.core.management.base import BaseCommand
from django.db.models.signals import post_save
from documents.models import Document
from ...mixins import Renderable
class Command(BaseCommand):
class Command(Renderable, BaseCommand):
help = """
This will rename all documents to match the latest filename format.
""".replace(" ", "")
def __init__(self, *args, **kwargs):
self.verbosity = 0
BaseCommand.__init__(self, *args, **kwargs)
def handle(self, *args, **options):
self.verbosity = options["verbosity"]
logging.getLogger().handlers[0].level = logging.ERROR
for document in tqdm.tqdm(Document.objects.all()):
@@ -2,15 +2,14 @@ import logging
from django.core.management.base import BaseCommand
from documents.classifier import load_classifier
from documents.classifier import DocumentClassifier, \
IncompatibleClassifierVersionError
from documents.models import Document
from ...mixins import Renderable
from ...signals.handlers import set_correspondent, set_document_type, set_tags
logger = logging.getLogger("paperless.management.retagger")
class Command(BaseCommand):
class Command(Renderable, BaseCommand):
help = """
Using the current classification model, assigns correspondents, tags
@@ -19,6 +18,10 @@ class Command(BaseCommand):
modified) after their initial import.
""".replace(" ", "")
def __init__(self, *args, **kwargs):
self.verbosity = 0
BaseCommand.__init__(self, *args, **kwargs)
def add_arguments(self, parser):
parser.add_argument(
"-c", "--correspondent",
@@ -59,16 +62,24 @@ class Command(BaseCommand):
def handle(self, *args, **options):
self.verbosity = options["verbosity"]
if options["inbox_only"]:
queryset = Document.objects.filter(tags__is_inbox_tag=True)
else:
queryset = Document.objects.all()
documents = queryset.distinct()
classifier = load_classifier()
classifier = DocumentClassifier()
try:
classifier.reload()
except (OSError, EOFError, IncompatibleClassifierVersionError) as e:
logging.getLogger(__name__).warning(
f"Cannot classify documents: {e}.")
classifier = None
for document in documents:
logger.info(
logging.getLogger(__name__).info(
f"Processing document {document.title}")
if options['correspondent']:
Loaded 100 of 154 files, more files were not shown because too many files have changed in this diff. Show more