1
0
mirror of https://github.com/sismics/docs.git synced 2025-12-14 18:26:17 +00:00

105 Commits
v1.7 ... v1.9

Author SHA1 Message Date
bgamard
ee6ed2bf0b v1.9 2021-01-25 21:27:22 +01:00
bgamard
57b67fee09 Closes #458: fix css glitch on mobile 2021-01-21 18:14:39 +01:00
bgamard
a6cbacae72 Closes #509: guest users cannot share and unshare 2021-01-21 17:58:36 +01:00
Vec7or
1e0f8e2484 Closes #472: redirect to previous URL after login 2021-01-21 17:44:48 +01:00
bgamard
bcb4c6d7b0 Merge remote-tracking branch 'origin/master' 2021-01-21 17:39:23 +01:00
bgamard
ea1d5907c1 #497: fix npe in unauthenticated cases 2021-01-21 17:39:01 +01:00
Vegard Hoff Walmsness
05bac38fc3 Norwegian language support 2021-01-14 20:20:16 +01:00
Cornelicorn
69746cd369 #486: Fix importer default file filter 2021-01-06 13:51:29 +01:00
Vec7or
ff3db531e5 Configure bcrypt work 2021-01-05 18:59:18 +01:00
bgamard
558de7ba3f README.md 2020-12-31 07:50:04 +01:00
Vec7or
af15116bf9 Upgrade bcrypt library + explain env variables 2020-12-31 07:46:00 +01:00
Benjamin Gamard
36e5a9747b Merge pull request #489 from Vec7or/fix-color-bug
Fix tag-colors inside inherited acl
2020-12-30 23:45:57 +01:00
Vec7or
1d66b47f5f Fix tag-colors inside inherited acl 2020-12-30 17:33:24 +01:00
Evil McJerkface
1346dd3616 Add option to specify a particular IMAP folder (aka "label" in Gmail) (#477) 2020-11-22 13:39:39 +01:00
Evil McJerkface
b6ec5e108b Added support for TLS & STARTTLS for SMTP connections. If port 465 is configured, TLS will be assumed. If port 587 is used, STARTTLS is assumed. (#478)
Closes #353: Added support for TLS & STARTTLS for SMTP connections
2020-11-19 10:15:40 +01:00
bgamard
5b2833350c Closes #391: change english date format to yyyy/mm/dd 2020-10-31 20:20:11 +01:00
bgamard
66acb380ab Closes #451: convert lob content to text for pgsql 2020-10-31 20:14:06 +01:00
bgamard
00c62f2ad4 Closes #467: italian translation 2020-10-31 20:11:55 +01:00
bgamard
7205863d95 Closes #469: make sure the IP sent by the forward proxy is not bigger than 45 chars 2020-10-23 19:31:27 +02:00
Pyrox
2a4274d583 Italian translation (#465) 2020-10-16 13:54:49 +02:00
bgamard
087184b598 Closes #466: remove duplicate translation resources 2020-10-16 13:53:12 +02:00
bgamard
e5600e0be7 add fallback for el language 2020-10-14 22:54:22 +02:00
Benjamin Gamard
964f3128d2 Merge pull request #463 from kazelot/dev-language-pl
Add  polish language
2020-10-14 22:49:57 +02:00
marcin
69905cdc55 Merge with last changes in master branch
Fix polish translation
2020-10-14 22:42:59 +02:00
marcin
bf4e277db7 Add translation to timeago.js
Update translation
2020-10-14 19:07:40 +02:00
bgamard
eaa7cca278 Closes #460: minification error following #455 2020-10-14 18:45:18 +02:00
marcin
0e115bb808 Translate to polish 2020-10-14 11:57:00 +02:00
marcin
1897f5567b Initial commit 2020-10-14 10:49:08 +02:00
bgamard
d647528b3c #455: greek translation of angular-timeago by @gdepountis 2020-10-12 19:36:59 +02:00
bgamard
07d42cdb9c Merge remote-tracking branch 'origin/master' 2020-10-12 10:44:41 +02:00
bgamard
dabb960c94 #455: greek translation by @gdepountis 2020-10-12 10:44:28 +02:00
Benjamin Gamard
c71e794051 Merge pull request #454 from vmario89/patch-3
Update de.json
2020-10-08 12:52:43 +02:00
Mario Voigt
1584c0cbb2 Update de.json
Small lang fix in de.json
2020-10-08 09:52:38 +02:00
bgamard
22f0f1abf4 Closes #453: load gravatar icons in HTTPS 2020-10-06 16:37:52 +02:00
Jean-Marc Tremeaux
205f92d093 Upgrade to JDK 11.0.8 2020-09-24 13:12:45 +02:00
Jean-Marc Tremeaux
7488ac15a7 Merge remote-tracking branch 'origin/master' 2020-09-24 12:45:08 +02:00
bgamard
44f5db993a #451: remove @Lob on file content 2020-09-13 17:58:28 +02:00
bgamard
f76eae23ca Merge remote-tracking branch 'origin/master' 2020-09-10 20:44:20 +02:00
bgamard
5e2a18f819 #451: update the file content with an Hibernate query instead of a native query 2020-09-10 20:42:51 +02:00
Benjamin Gamard
2f6e5d53c2 Merge pull request #449 from muhsinkutay/patch-1
Fix typo in English translation
2020-09-08 02:02:59 +02:00
muhsinkutay
50e6c4d965 Update en.json
Line 6: Misspelling
2020-09-08 02:51:53 +03:00
bgamard
3ad0554a7d use org.apache.directory.api for LDAP instead of apacheds 2020-08-29 19:10:14 +02:00
bgamard
113ec78c67 import less apacheds dependencies 2020-08-28 19:33:58 +02:00
bgamard
f814927eca update README.md 2020-08-28 18:10:28 +02:00
bgamard
a9719feeec LDAP support, courtesy of an anonymous donator 2020-08-28 18:09:54 +02:00
bgamard
6dc4f1b448 #423: fulltext search by default 2020-08-28 17:47:07 +02:00
bgamard
e1fa17691d Merge remote-tracking branch 'origin/master' 2020-08-28 17:34:03 +02:00
bgamard
42e61d6e1f #423: fulltext search by default 2020-08-28 17:33:27 +02:00
Jamie Magee
2bf3e6bd3c Danish language support (#438) 2020-08-02 23:32:29 +02:00
Benjamin Gamard
608b2f868d Doc about prebuilt Docker image for bulk importer 2020-06-24 21:54:09 +02:00
Benjamin Gamard
46638bab5b Build and push docs-importer to Docker Hub 2020-06-24 21:18:37 +02:00
Carl Reid
4607362e46 Add file filter to importer (#426) 2020-06-23 22:31:49 +02:00
Cornelicorn
041b2dfcc1 Fix tag-adding bugs of the importer (#427) 2020-06-21 14:48:13 +02:00
Carl Reid
7ad0dd43e2 Remove COPY node_modules from Dockerfile (#425) 2020-06-21 13:45:23 +02:00
buherman11
35339f7328 Fixed sending workflow emails to previous assignee (#422) 2020-06-10 12:04:09 +02:00
Gabisonfire
e474e7cd75 Added option to copy a file before it is deleted after being imported (#418) 2020-06-07 20:13:04 +02:00
Cornelicorn
612fab2aef Improve the file importer (#415)
Improve the bulk importer (tags by filename, document language, Docker container)
2020-05-22 15:18:19 +02:00
bgamard
3f67bd471b increase default heap space to 1GB 2020-05-19 15:33:23 +02:00
bgamard
cb29dcd6cc handle all content extraction errors 2020-05-19 15:11:05 +02:00
bgamard
d428e89c30 #412: process files outside of a transaction 2020-05-19 14:44:41 +02:00
bgamard
9b2aeb7480 add temporary logs 2020-05-19 14:05:34 +02:00
bgamard
d9ad69c7ff add more log to debug processing indicator 2020-05-17 21:58:13 +02:00
bgamard
16fc058264 Merge remote-tracking branch 'origin/master' 2020-05-17 21:00:30 +02:00
bgamard
520b143165 #412: better handle concurrent updates and async listeners 2020-05-17 21:00:01 +02:00
cadast
95c37a03f8 Improve Inbox Scanning (#407)
closes #386: delete emails after import + closes #405: auto tag documents imported by email
2020-05-14 13:59:11 +02:00
bgamard
0d058b9c9c at least 2 threads for background work 2020-05-07 11:09:11 +02:00
bgamard
7c72b5e69b #401: importer: truncate document title to allowed size 2020-05-06 13:56:45 +02:00
bgamard
3ec254e908 #400: limit async bus to (cpu cores / 2) threads 2020-05-06 11:35:45 +02:00
bgamard
fda13c004e Merge remote-tracking branch 'origin/master' 2020-04-21 21:13:54 +02:00
bgamard
3af85eeea6 bump importer version 2020-04-21 21:13:20 +02:00
lord-lawnmower
c08616e6df Latvian Language Support (#390) 2020-04-13 21:04:47 +02:00
Antti Tapio
7faa0f8a54 Finnish and Swedish language support (#389) 2020-04-12 18:27:06 +02:00
bgamard
26c5fe2e69 next dev iteration 2020-03-26 20:06:58 +01:00
bgamard
6bdaa8352b update README.md 2020-03-26 20:00:44 +01:00
bgamard
6367a1fd15 v1.8 2020-03-26 19:57:10 +01:00
bgamard
2c5ff64d42 Closes #387: validation username and group name in UI 2020-03-25 19:02:50 +01:00
bgamard
e614cb41d8 update feedback api url 2020-03-25 18:13:49 +01:00
bgamard
82737e2280 Closes #334: highlight previously opened file 2020-03-07 17:56:01 +01:00
bgamard
3b5c27096b Closes #350: better relations widget 2020-03-07 17:46:40 +01:00
bgamard
8a85830bd3 #381: date fields manually editable 2020-03-07 00:36:03 +01:00
bgamard
19ac90688e upgrade guava 2020-02-19 20:47:26 +01:00
bgamard
5f4a6bc462 #336: fix search by type 2020-02-19 18:00:13 +01:00
bgamard
4c7f3166d4 Closes #332: tag text color legibility 2020-02-15 23:00:35 +01:00
bgamard
4233f4dd88 Closes #317: edit tag color hex code manually 2020-02-15 22:38:06 +01:00
bgamard
bd09312418 Closes #336: search document by file mime type 2020-02-15 22:05:04 +01:00
bgamard
11ab07b238 Closes #333: fix overflow in document table with a lot of tags 2020-02-15 15:44:32 +01:00
bgamard
d2e2f089fb update README for build deps 2020-02-14 21:48:45 +01:00
bgamard
d619f98de7 Closes #379: spaces and colons not allowed in tag name 2020-02-14 21:40:13 +01:00
bgamard
89228a52dc Closes #378: silence useless log from Jersey 2020-02-14 21:33:34 +01:00
bgamard
90a49efa4a Closes #373: high quality PDF to image conversion before OCR 2020-02-13 17:43:07 +01:00
junpet
a7423caeb1 Add Hungarian Language Support (#369)
Add Hungarian language support
2020-01-29 15:44:44 +01:00
bgamard
6f31a2c228 Closes #366: get the private key from the right user when processing files 2020-01-21 12:54:50 +01:00
Benjamin Gamard
fc98b0882f Merge remote-tracking branch 'origin/master' 2019-12-28 13:54:45 +01:00
Benjamin Gamard
dff05967ea Closes #363: pgsql compatibility for table alias in update queries 2019-12-28 13:53:42 +01:00
Benjamin Gamard
ec836a2f9d Update README.md 2019-10-13 01:24:10 +02:00
Benjamin Gamard
737c85cf00 Update README.md 2019-10-13 01:23:03 +02:00
Benjamin Gamard
ff7b07f464 Create FUNDING.yml 2019-10-12 23:05:35 +02:00
Mario Voigt
19422b5afa Fixed some language issue (#352) 2019-08-26 14:19:19 +02:00
Benjamin Gamard
6b93e413b6 #321: remove duplicate contributors 2019-06-03 11:45:38 +02:00
Mario Voigt
ab72736bcc Update de.json (#322)
Added missing german translation strings
2019-05-25 18:53:02 +02:00
Benjamin Gamard
38939e5d05 #314: force file content in utf8 2019-05-22 15:36:50 +02:00
Benjamin Gamard
1a90a0e0ad next dev iteration 2019-05-21 16:17:54 +02:00
Jean-Marc Tremeaux
94252de73f Merge remote-tracking branch 'origin/master' 2018-10-23 18:16:35 +02:00
Jean-Marc Tremeaux
d43072663e Merge remote-tracking branch 'origin/master' 2017-03-21 09:04:20 +01:00
Jean-Marc Tremeaux
eb3562567d Fix dockerfile 2017-03-21 08:51:58 +01:00
135 changed files with 4830 additions and 945 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [jendib]

6
.gitignore vendored
View File

@@ -11,6 +11,6 @@
*.iml
node_modules
import_test
docs-importer-linux
docs-importer-macos
docs-importer-win.exe
teedy-importer-linux
teedy-importer-macos
teedy-importer-win.exe

View File

@@ -4,7 +4,7 @@ language: java
before_install:
- sudo add-apt-repository -y ppa:mc3man/trusty-media
- sudo apt-get -qq update
- sudo apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb
- sudo apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb tesseract-ocr-hun tesseract-ocr-fin tesseract-ocr-swe tesseract-ocr-lav tesseract-ocr-dan tesseract-ocr-nor
- sudo apt-get -y -q install haveged && sudo service haveged start
after_success:
- |
@@ -17,6 +17,13 @@ after_success:
docker tag $REPO:$COMMIT $REPO:$TAG
docker tag $REPO:$COMMIT $REPO:travis-$TRAVIS_BUILD_NUMBER
docker push $REPO
cd docs-importer
export REPO=sismics/docs-importer
export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi`
docker build -f Dockerfile -t $REPO:$COMMIT .
docker tag $REPO:$COMMIT $REPO:$TAG
docker tag $REPO:$COMMIT $REPO:travis-$TRAVIS_BUILD_NUMBER
docker push $REPO
fi
env:
global:

View File

@@ -1,7 +1,7 @@
FROM sismics/ubuntu-jetty:9.4.12
FROM sismics/ubuntu-jetty:9.4.12-2
MAINTAINER b.gamard@sismics.com
RUN apt-get update && apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb && \
RUN apt-get update && apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb tesseract-ocr-hun tesseract-ocr-fin tesseract-ocr-swe tesseract-ocr-lav tesseract-ocr-dan tesseract-ocr-nor && \
apt-get clean && rm -rf /var/lib/apt/lists/*
# Remove the embedded javax.mail jar from Jetty
@@ -9,3 +9,5 @@ RUN rm -f /opt/jetty/lib/mail/javax.mail.glassfish-*.jar
ADD docs.xml /opt/jetty/webapps/docs.xml
ADD docs-web/target/docs-web-*.war /opt/jetty/webapps/docs.war
ENV JAVA_OPTIONS -Xmx1g

142
README.md
View File

@@ -2,17 +2,14 @@
<img src="https://teedy.io/img/github-title.png" alt="Teedy" width=500 />
</h3>
[![Twitter: @teedyio](https://img.shields.io/badge/contact-@teedyio-blue.svg?style=flat)](https://twitter.com/teedyio)
[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
[![Build Status](https://secure.travis-ci.org/sismics/docs.png)](http://travis-ci.org/sismics/docs)
Teedy is an open source, lightweight document management system for individuals and businesses.
**Discuss it on [Product Hunt](https://www.producthunt.com/posts/sismics-docs) 🦄**
<hr />
<h2 align="center">
Sismics Docs is now called Teedy! You can still find our cloud and support offer on <a href="https://teedy.io">teedy.io</a> ✨
<a href="https://github.com/users/jendib/sponsorship">Sponsor this project if you use and appreciate it!</a> ✨
</h2>
<hr />
@@ -22,6 +19,7 @@ Demo
----
A demo is available at [demo.teedy.io](https://demo.teedy.io)
- Guest login is enabled with read access on all documents
- "admin" login with "admin" password
- "demo" login with "password" password
@@ -31,6 +29,7 @@ Features
- Responsive user interface
- Optical character recognition
- LDAP authentication ![New!](https://www.sismics.com/public/img/new.png)
- Support image, PDF, ODT, DOCX, PPTX files
- Video file support
- Flexible search engine with suggestions and highlighting
@@ -62,17 +61,130 @@ Install with Docker
A preconfigured Docker image is available, including OCR and media conversion tools, listening on port 8080. The database is an embedded H2 database but PostgreSQL is also supported for more performance.
**The default admin password is "admin". Don't forget to change it before going to production.**
- Master branch, can be unstable. Not recommended for production use: `sismics/docs:latest`
- Latest stable version: `sismics/docs:v1.7`
- Latest stable version: `sismics/docs:v1.8`
The data directory is `/data`. Don't forget to mount a volume on it.
To build external URL, the server is expecting a `DOCS_BASE_URL` environment variable (for example https://teedy.mycompany.com)
### Available environment variables
- General
- `DOCS_BASE_URL`: The base url used by the application. Generated url's will be using this as base.
- `DOCS_GLOBAL_QUOTA`: Defines the default quota applying to all users.
- `DOCS_BCRYPT_WORK`: Defines the work factor which is used for password hashing. The default is `10`. This value may be `4...31` including `4` and `31`. The specified value will be used for all new users and users changing their password. Be aware that setting this factor to high can heavily impact login and user creation performance.
- Admin
- `DOCS_ADMIN_EMAIL_INIT`: Defines the e-mail-address the admin user should have upon initialization.
- `DOCS_ADMIN_PASSWORD_INIT`: Defines the password the admin user should have upon initialization. Needs to be a bcrypt hash. **Be aware that `$` within the hash have to be escaped with a second `$`.**
- Database
- `DATABASE_URL`: The jdbc connection string to be used by `hibernate`.
- `DATABASE_USER`: The user which should be used for the database connection.
- `DATABASE_PASSWORD`: The password to be used for the database connection.
- Language
- `DOCS_DEFAULT_LANGUAGE`: The language which will be used as default. Currently supported values are:
- `eng`, `fra`, `ita`, `deu`, `spa`, `por`, `pol`, `rus`, `ukr`, `ara`, `hin`, `chi_sim`, `chi_tra`, `jpn`, `tha`, `kor`, `nld`, `tur`, `heb`, `hun`, `fin`, `swe`, `lav`, `dan`
- E-Mail
- `DOCS_SMTP_HOSTNAME`: Hostname of the SMTP-Server to be used by Teedy.
- `DOCS_SMTP_PORT`: The port which should be used.
- `DOCS_SMTP_USERNAME`: The username to be used.
- `DOCS_SMTP_PASSWORD`: The password to be used.
### Examples
In the following examples some passwords are exposed in cleartext. This was done in order to keep the examples simple. We strongly encourage you to use variables with an `.env` file or other means to securely store your passwords.
#### Using the internal database
```yaml
version: '3'
services:
# Teedy Application
teedy-server:
image: sismics/docs:v1.8
restart: unless-stopped
ports:
# Map internal port to host
- 8080:8080
environment:
# Base url to be used
DOCS_BASE_URL: "https://docs.example.com"
# Set the admin email
DOCS_ADMIN_EMAIL_INIT: "admin@example.com"
# Set the admin password (in this example: "superSecure")
DOCS_ADMIN_PASSWORD_INIT: "$$2a$$05$$PcMNUbJvsk7QHFSfEIDaIOjk1VI9/E7IPjTKx.jkjPxkx2EOKSoPS"
volumes:
- ./docs/data:/data
```
#### Using PostgreSQL
```yaml
version: '3'
services:
# Teedy Application
teedy-server:
image: sismics/docs:v1.8
restart: unless-stopped
ports:
# Map internal port to host
- 8080:8080
environment:
# Base url to be used
DOCS_BASE_URL: "https://docs.example.com"
# Set the admin email
DOCS_ADMIN_EMAIL_INIT: "admin@example.com"
# Set the admin password (in this example: "superSecure")
DOCS_ADMIN_PASSWORD_INIT: "$$2a$$05$$PcMNUbJvsk7QHFSfEIDaIOjk1VI9/E7IPjTKx.jkjPxkx2EOKSoPS"
# Setup the database connection. "teedy-db" is the hostname
# and "teedy" is the name of the database the application
# will connect to.
DATABASE_URL: "jdbc:postgresql://teedy-db:5432/teedy"
DATABASE_USER: "teedy_db_user"
DATABASE_PASSWORD: "teedy_db_password"
volumes:
- ./docs/data:/data
networks:
- docker-internal
- internet
depends_on:
- teedy-db
# DB for Teedy
teedy-db:
image: postgres:13.1-alpine
restart: unless-stopped
expose:
- 5432
environment:
POSTGRES_USER: "teedy_db_user"
POSTGRES_PASSWORD: "teedy_db_password"
POSTGRES_DB: "teedy"
volumes:
- ./docs/db:/var/lib/postgresql/data
networks:
- docker-internal
networks:
# Network without internet access. The db does not need
# access to the host network.
docker-internal:
driver: bridge
internal: true
internet:
driver: bridge
```
Manual installation
-------------------
#### Requirements
- Java 8 with the [Java Cryptography Extension](http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html)
- Tesseract 3 or 4 for OCR
- ffmpeg for video thumbnails
@@ -80,19 +192,20 @@ Manual installation
- A webapp server like [Jetty](http://eclipse.org/jetty/) or [Tomcat](http://tomcat.apache.org/)
#### Download
The latest release is downloadable here: <https://github.com/sismics/docs/releases> in WAR format.
**The default admin password is "admin". Don't forget to change it before going to production.**
How to build Teedy from the sources
----------------------------------
Prerequisites: JDK 8 with JCE, Maven 3, Tesseract 3 or 4
Prerequisites: JDK 8 with JCE, Maven 3, NPM, Grunt, Tesseract 3 or 4
Teedy is organized in several Maven modules:
- docs-core
- docs-web
- docs-web-common
- docs-core
- docs-web
- docs-web-common
First off, clone the repository: `git clone git://github.com/sismics/docs.git`
or download the sources from GitHub.
@@ -124,17 +237,6 @@ All contributions are more than welcomed. Contributions may close an issue, fix
The `master` branch is the default and base branch for the project. It is used for development and all Pull Requests should go there.
Community
---------
Get updates on Teedy's development and chat with the project maintainers:
- Follow [@teedyio on Twitter](https://twitter.com/teedyio)
- Read and subscribe to [The Official Teedy Blog](https://blog.teedy.io/)
- Check the [Official Website](https://teedy.io)
- Join us [on Facebook](https://www.facebook.com/teedyio)
License
-------

View File

@@ -34,6 +34,7 @@ public class LanguageAdapter extends BaseAdapter {
languageList.add(new Language("fra", R.string.language_french, R.drawable.fra));
languageList.add(new Language("eng", R.string.language_english, R.drawable.eng));
languageList.add(new Language("deu", R.string.language_german, R.drawable.deu));
languageList.add(new Language("pol", R.string.language_polish, R.drawable.pol));
}
@Override

View File

@@ -39,7 +39,9 @@ public class SearchQueryBuilder {
*/
public SearchQueryBuilder simpleSearch(String simpleSearch) {
if (isValid(simpleSearch)) {
query.append(SEARCH_SEPARATOR).append(simpleSearch);
query.append(SEARCH_SEPARATOR)
.append("simple:")
.append(simpleSearch);
}
return this;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Validation -->
<string name="validate_error_email">Nieprawidłowy email</string>
<string name="validate_error_length_min">Za krótki (min. %d)</string>
<string name="validate_error_length_max">Za długi (max. %d)</string>
<string name="validate_error_required">Wymagany</string>
<string name="validate_error_alphanumeric">Tylko litery i cyfry</string>
<!-- App -->
<string name="app_name" translatable="false">Teedy</string>
<string name="drawer_open">Otwórz szufladę nawigacji</string>
<string name="drawer_close">Zamknij szufladę nawigacji</string>
<string name="login_explain"><![CDATA[Aby rozpocząć, musisz pobrać i zainstalować serwer Teedy na <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> i poniżej wprowadzić adres]]></string>
<string name="server">Serwer</string>
<string name="username">Użytkownik</string>
<string name="password">Hasło</string>
<string name="login">Zaloguj</string>
<string name="ok">OK</string>
<string name="cancel">Anuluj</string>
<string name="login_fail_title">Błąd logowania</string>
<string name="login_fail">Nieprawidłowa nazwa użytkownika lub hasło</string>
<string name="network_error_title">Błąd sieci</string>
<string name="network_error">Błąd sieci, sprawdź połączenie z interneterm oraz adres URL serwera</string>
<string name="invalid_url_title">Nieprawidłowy adres URL</string>
<string name="invalid_url">Sprawdź adres URL serwera i spróbuj ponownie</string>
<string name="crash_toast_text">Wystąpiła awaria, wysłano raport w celu rozwiązania tego problemu</string>
<string name="created_date">Data utworzenia</string>
<string name="download_file">Pobierz bieżący plik</string>
<string name="download_document">Pobierz</string>
<string name="action_search">Znadź dokumenty</string>
<string name="all_documents">Wszystkie dokumenty</string>
<string name="shared_documents">Udostępnione dokumenty</string>
<string name="all_tags">Wszystkie etykiety</string>
<string name="no_tags">Brak etykiet</string>
<string name="error_loading_tags">Błąd ładowania etykiet</string>
<string name="no_documents">Brak dokumentów</string>
<string name="error_loading_documents">Błąd ładowania dokumentów</string>
<string name="no_files">Brak plików</string>
<string name="error_loading_files">Błąd ładowania plików</string>
<string name="new_document">Nowy dokument</string>
<string name="share">Udostępnij</string>
<string name="close">Zamknij</string>
<string name="add">Dodaj</string>
<string name="add_share_hint">Nazwa udostępnienia (opcjonalnie)</string>
<string name="document_not_shared">Ten dokument nie jest obecnie udostępniony</string>
<string name="delete_share">Usuń udostępnienie</string>
<string name="send_share">Wyślij link udostępnienia</string>
<string name="error_loading_shares">Błąd ładowania udostępnień</string>
<string name="error_adding_share">Błąd dodawania udostępnienia</string>
<string name="share_default_name">Udostępnij link</string>
<string name="error_deleting_share">Błąd usuwania udostępnienia</string>
<string name="send_share_to">Wyślij link udostępnienia do</string>
<string name="upload_file">dodaj plik</string>
<string name="upload_from">Przeslij plik z</string>
<string name="settings">ustawienia</string>
<string name="logout">Wyloguj</string>
<string name="version">Wersja</string>
<string name="build">Kompilacja</string>
<string name="pref_advanced_category">Ustawienia zaawansowane</string>
<string name="pref_about_category">O programie</string>
<string name="pref_github">GitHub</string>
<string name="pref_issue">Zgłoś błąd</string>
<string name="pref_clear_cache_title">Wyczyść cache</string>
<string name="pref_clear_cache_summary">Wyczyść podręczne pliki</string>
<string name="pref_clear_cache_success">Cache wyczyszczony</string>
<string name="pref_clear_history_title">Wyczyść historię wyszukiwania</string>
<string name="pref_clear_history_summary">Opróżnij ostatnie sugestie wyszukiwania</string>
<string name="pref_clear_history_success">Historia wyszukiwania wyczyszczona</string>
<string name="pref_cache_size">Rozmiar cache</string>
<string name="language_french" translatable="false">Francuski</string>
<string name="language_english" translatable="false">Angielski</string>
<string name="language_german" translatable="false">Niemiecki</string>
<string name="language_polish" translatable="false">Polski</string>
<string name="save">Zapisz</string>
<string name="edit_document">Edytuj</string>
<string name="error_editing_document">Błąd sieci, spróbuj ponownie</string>
<string name="please_wait">Proszę czekać</string>
<string name="document_editing_message">Wysyłam twoje dane</string>
<string name="delete_document">Usuń</string>
<string name="delete_document_title">Usuń dokument</string>
<string name="delete_document_message">Naprawdę chcesz usunąć dokument i powiązane z nim pliki?</string>
<string name="document_delete_failure">Błąd sieci w czasie usuwania tego dokumentu</string>
<string name="document_deleting_message">Usuwanie dokumentu</string>
<string name="delete_file_title">Usuń plik</string>
<string name="delete_file_message">Naprawdę chcesz usunąć ten plik?</string>
<string name="file_delete_failure">Błąd sieci w czasie usuwania bieżącego pliku</string>
<string name="file_deleting_message">Usuwanie pliku</string>
<string name="error_reading_file">Błąd podczas odczytu pliku</string>
<string name="upload_notification_title">Teedy</string>
<string name="upload_notification_message">Przesyłanie nowego pliku do dokumentu</string>
<string name="upload_notification_error">Błąd przsyłania nowego pliku</string>
<string name="delete_file">Usuń bieżący plik</string>
<string name="advanced_search">Zaawansowane wyszukiwanie</string>
<string name="search">Znajdź</string>
<string name="add_tags">Dodaj eytkiety</string>
<string name="creation_date">Data utworzenia</string>
<string name="description">Opis</string>
<string name="title">Tytuł</string>
<string name="simple_search">Proste wyszukiwanie</string>
<string name="fulltext_search">Wyszukiwanie pełnotekstowe</string>
<string name="creator">Autor</string>
<string name="after_date">Po dacie</string>
<string name="before_date">Przed datą</string>
<string name="search_tags">Znajdź etykiety</string>
<string name="all_languages">Wszystkie języki</string>
<string name="toggle_informations">Przełącz informacje</string>
<string name="who_can_access">Kto ma dostęp</string>
<string name="comments">Komentarze</string>
<string name="no_comments">Brak komentarzy</string>
<string name="error_loading_comments">Błąd ładowania komentarzy</string>
<string name="send">Wyślij</string>
<string name="add_comment">Dodaj komentarz</string>
<string name="comment_add_failure">Błąd dodawania komentarza</string>
<string name="adding_comment">Dodawanie komentarza</string>
<string name="comment_delete">Usuń komentarz</string>
<string name="deleting_comment">Usuwanie komentarza</string>
<string name="error_deleting_comment">Błąd usuwania komentarza</string>
<string name="export_pdf">PDF</string>
<string name="download">Pobierz</string>
<string name="margin">Margines</string>
<string name="fit_image_to_page">Dostosuj obraz do strony</string>
<string name="export_comments">Eksport komentarzy</string>
<string name="export_metadata">Eksport metadanych</string>
<string name="mm">mm</string>
<string name="download_file_title">Eksport plików Teedy</string>
<string name="download_document_title">Eksport dokumentu Teedy</string>
<string name="download_pdf_title">Eksport Teedy jako PDF</string>
<string name="latest_activity">Ostatnie aktywności</string>
<string name="activity">Aktywności</string>
<string name="email">E-mail</string>
<string name="storage_quota">Limit magazynu</string>
<string name="storage_display">%1$d/%2$d MB</string>
<string name="validation_code">Kod weryfikujący</string>
<string name="shared">Udostępnienie</string>
<string name="language">Język</string>
<string name="coverage">Zakres</string>
<string name="type">Rodzaj</string>
<string name="source">Źródło</string>
<string name="format">Format</string>
<string name="publisher">Udostępniający</string>
<string name="identifier">Identifikator</string>
<string name="subject">temat</string>
<string name="rights">Prawa</string>
<string name="contributors">Współtwórcy</string>
<string name="relations">Powiązania</string>
<!-- Audit log -->
<string name="auditlog_Acl">ACL</string>
<string name="auditlog_Comment">Komentarz</string>
<string name="auditlog_Document">Dokument</string>
<string name="auditlog_File">Plik</string>
<string name="auditlog_Group">Grupa</string>
<string name="auditlog_Route">Przepływ</string>
<string name="auditlog_RouteModel">Model przepływu</string>
<string name="auditlog_Tag">Etykieta</string>
<string name="auditlog_User">Użytkownik</string>
<string name="auditlog_Webhook">Webhook</string>
<string name="auditlog_created">utworzony</string>
<string name="auditlog_updated">zaktualizowany</string>
<string name="auditlog_deleted">usunięty</string>
</resources>

View File

@@ -72,6 +72,7 @@
<string name="language_french" translatable="false">Français</string>
<string name="language_english" translatable="false">English</string>
<string name="language_german" translatable="false">Deutsch</string>
<string name="language_polish" translatable="false">Polski</string>
<string name="save">Save</string>
<string name="edit_document">Edit</string>
<string name="error_editing_document">Network error, please try again</string>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.7</version>
<version>1.9</version>
<relativePath>..</relativePath>
</parent>
@@ -91,10 +91,11 @@
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<groupId>at.favre.lib</groupId>
<artifactId>bcrypt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
@@ -131,7 +132,12 @@
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.apache.directory.api</groupId>
<artifactId>api-all</artifactId>
</dependency>
<!-- Only there to read old index and rebuild them -->
<dependency>
<groupId>org.apache.lucene</groupId>
@@ -189,7 +195,26 @@
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- JDK 11 JAXB dependencies -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>

View File

@@ -42,5 +42,21 @@ public enum ConfigType {
INBOX_PORT,
INBOX_USERNAME,
INBOX_PASSWORD,
INBOX_TAG
INBOX_FOLDER,
INBOX_TAG,
INBOX_AUTOMATIC_TAGS,
INBOX_DELETE_IMPORTED,
/**
* LDAP connection.
*/
LDAP_ENABLED,
LDAP_HOST,
LDAP_PORT,
LDAP_ADMIN_DN,
LDAP_ADMIN_PASSWORD,
LDAP_BASE_DN,
LDAP_FILTER,
LDAP_DEFAULT_EMAIL,
LDAP_DEFAULT_STORAGE
}

View File

@@ -18,13 +18,18 @@ public class Constants {
/**
* Administrator's default password ("admin").
*/
public static final String DEFAULT_ADMIN_PASSWORD = "$2a$05$6Ny3TjrW3aVAL1or2SlcR.fhuDgPKp5jp.P9fBXwVNePgeLqb4i3C";
public static final String DEFAULT_ADMIN_PASSWORD = "$2y$10$xg0EEKVUehutDI1m6qQhVeFz7SMQMl1jQzjf2KkVsR2c7aV2vyyjK";
/**
* Administrator's default email.
*/
public static final String DEFAULT_ADMIN_EMAIL = "admin@localhost";
/**
* Bcrypt default work factor
*/
public static final int DEFAULT_BCRYPT_WORK = 10;
/**
* Guest user ID.
*/
@@ -38,7 +43,7 @@ public class Constants {
/**
* Supported document languages.
*/
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld", "tur", "heb");
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld", "tur", "heb", "hun", "fin", "swe", "lav", "dan", "nor");
/**
* Base URL environment variable.
@@ -73,6 +78,11 @@ public class Constants {
*/
public static final String ADMIN_EMAIL_INIT_ENV = "DOCS_ADMIN_EMAIL_INIT";
/**
* Work factor to be used by Bcrypt
*/
public static final String BCRYPT_WORK_ENV = "DOCS_BCRYPT_WORK";
/**
* Expiration time of the password recovery in hours.
*/

View File

@@ -128,6 +128,9 @@ public class AclDao {
if (SecurityUtil.skipAclCheck(targetIdList)) {
return true;
}
if (targetIdList.isEmpty()) {
return false;
}
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select a.ACL_ID_C from T_ACL a ");

View File

@@ -56,7 +56,7 @@ public class ContributorDao {
@SuppressWarnings("unchecked")
public List<ContributorDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
StringBuilder sb = new StringBuilder("select distinct u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
sb.append(" join T_USER u on u.USE_ID_C = c.CTR_IDUSER_C ");
sb.append(" where c.CTR_IDDOC_C = :documentId ");
Query q = em.createNativeQuery(sb.toString());

View File

@@ -232,7 +232,7 @@ public class DocumentDao {
*/
public void updateFileId(Document document) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query query = em.createNativeQuery("update T_DOCUMENT d set d.DOC_IDFILE_C = :fileId, d.DOC_UPDATEDATE_D = :updateDate where d.DOC_ID_C = :id");
Query query = em.createNativeQuery("update T_DOCUMENT d set DOC_IDFILE_C = :fileId, DOC_UPDATEDATE_D = :updateDate where d.DOC_ID_C = :id");
query.setParameter("updateDate", new Date());
query.setParameter("fileId", document.getFileId());
query.setParameter("id", document.getId());

View File

@@ -153,7 +153,7 @@ public class FileDao {
return file;
}
/**
* Gets a file by its ID.
*

View File

@@ -1,7 +1,13 @@
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import at.favre.lib.crypto.bcrypt.BCrypt;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.criteria.UserCriteria;
import com.sismics.docs.core.dao.dto.UserDto;
import com.sismics.docs.core.model.jpa.User;
@@ -11,8 +17,6 @@ import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import org.joda.time.DateTime;
import org.mindrot.jbcrypt.BCrypt;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
@@ -26,6 +30,11 @@ import java.util.*;
* @author jtremeaux
*/
public class UserDao {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(UserDao.class);
/**
* Authenticates an user.
*
@@ -39,7 +48,8 @@ public class UserDao {
q.setParameter("username", username);
try {
User user = (User) q.getSingleResult();
if (!BCrypt.checkpw(password, user.getPassword()) || user.getDisableDate() != null) {
BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), user.getPassword());
if (!result.verified || user.getDisableDate() != null) {
return null;
}
return user;
@@ -277,7 +287,21 @@ public class UserDao {
* @return Hashed password
*/
private String hashPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt());
int bcryptWork = Constants.DEFAULT_BCRYPT_WORK;
String envBcryptWork = System.getenv(Constants.BCRYPT_WORK_ENV);
if (envBcryptWork != null) {
try {
int envBcryptWorkInt = Integer.parseInt(envBcryptWork);
if (envBcryptWorkInt >= 4 && envBcryptWorkInt <= 31) {
bcryptWork = envBcryptWorkInt;
} else {
log.warn(Constants.BCRYPT_WORK_ENV + " needs to be in range 4...31. Falling back to " + Constants.DEFAULT_BCRYPT_WORK + ".");
}
} catch (NumberFormatException e) {
log.warn(Constants.BCRYPT_WORK_ENV + " needs to be a number in range 4...31. Falling back to " + Constants.DEFAULT_BCRYPT_WORK + ".");
}
}
return BCrypt.withDefaults().hashToString(bcryptWork, password.toCharArray());
}
/**

View File

@@ -76,6 +76,11 @@ public class DocumentCriteria {
* A route is active.
*/
private Boolean activeRoute;
/**
* MIME type of a file.
*/
private String mimeType;
public List<String> getTargetIdList() {
return targetIdList;
@@ -181,4 +186,12 @@ public class DocumentCriteria {
public void setActiveRoute(Boolean activeRoute) {
this.activeRoute = activeRoute;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
}

View File

@@ -1,7 +1,6 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.Document;
/**
* Document created event.
@@ -10,32 +9,22 @@ import com.sismics.docs.core.model.jpa.Document;
*/
public class DocumentCreatedAsyncEvent extends UserEvent {
/**
* Created document.
* Document ID.
*/
private Document document;
/**
* Getter of document.
*
* @return the document
*/
public Document getDocument() {
return document;
private String documentId;
public String getDocumentId() {
return documentId;
}
/**
* Setter of document.
*
* @param document document
*/
public void setDocument(Document document) {
this.document = document;
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("document", document)
.toString();
.add("documentId", documentId)
.toString();
}
}

View File

@@ -1,7 +1,6 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.File;
/**
* File deleted event.
@@ -10,22 +9,22 @@ import com.sismics.docs.core.model.jpa.File;
*/
public class FileDeletedAsyncEvent extends UserEvent {
/**
* Deleted file.
* File ID.
*/
private File file;
public File getFile() {
return file;
private String fileId;
public String getFileId() {
return fileId;
}
public void setFile(File file) {
this.file = file;
public void setFileId(String fileId) {
this.fileId = fileId;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("file", file)
.add("fileId", fileId)
.toString();
}
}

View File

@@ -1,7 +1,6 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.File;
import java.nio.file.Path;
@@ -12,9 +11,9 @@ import java.nio.file.Path;
*/
public abstract class FileEvent extends UserEvent {
/**
* Created file.
* File ID.
*/
private File file;
private String fileId;
/**
* Language of the file.
@@ -25,15 +24,15 @@ public abstract class FileEvent extends UserEvent {
* Unencrypted original file.
*/
private Path unencryptedFile;
public File getFile() {
return file;
public String getFileId() {
return fileId;
}
public void setFile(File file) {
this.file = file;
public void setFileId(String fileId) {
this.fileId = fileId;
}
public String getLanguage() {
return language;
}
@@ -54,7 +53,7 @@ public abstract class FileEvent extends UserEvent {
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("file", file)
.add("fileId", fileId)
.add("language", language)
.toString();
}

View File

@@ -3,9 +3,11 @@ package com.sismics.docs.core.listener.async;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.ContributorDao;
import com.sismics.docs.core.dao.DocumentDao;
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,15 +36,22 @@ public class DocumentCreatedAsyncListener {
}
TransactionUtil.handle(() -> {
// Fetch a fresh document
Document document = new DocumentDao().getById(event.getDocumentId());
if (document == null) {
// The document has been deleted since
return;
}
// Add the first contributor (the creator of the document)
ContributorDao contributorDao = new ContributorDao();
Contributor contributor = new Contributor();
contributor.setDocumentId(event.getDocument().getId());
contributor.setDocumentId(event.getDocumentId());
contributor.setUserId(event.getUserId());
contributorDao.create(contributor);
// Update index
AppContext.getInstance().getIndexingHandler().createDocument(event.getDocument());
AppContext.getInstance().getIndexingHandler().createDocument(document);
});
}
}

View File

@@ -4,7 +4,6 @@ import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.util.FileUtil;
import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger;
@@ -35,12 +34,11 @@ public class FileDeletedAsyncListener {
}
// Delete the file from storage
File file = event.getFile();
FileUtil.delete(file);
FileUtil.delete(event.getFileId());
TransactionUtil.handle(() -> {
// Update index
AppContext.getInstance().getIndexingHandler().deleteDocument(file.getId());
AppContext.getInstance().getIndexingHandler().deleteDocument(event.getFileId());
});
}
}

View File

@@ -28,6 +28,7 @@ import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.concurrent.atomic.AtomicReference;
/**
* Listener on file processing.
@@ -52,15 +53,7 @@ public class FileProcessingAsyncListener {
log.info("File created event: " + event.toString());
}
TransactionUtil.handle(() -> {
// Generate thumbnail, extract content
processFile(event);
// Update index
AppContext.getInstance().getIndexingHandler().createFile(event.getFile());
});
FileUtil.endProcessingFile(event.getFile().getId());
processFile(event, true);
}
/**
@@ -71,43 +64,84 @@ public class FileProcessingAsyncListener {
@Subscribe
@AllowConcurrentEvents
public void on(final FileUpdatedAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("File updated event: " + event.toString());
}
log.info("File updated event: " + event.toString());
TransactionUtil.handle(() -> {
// Generate thumbnail, extract content
processFile(event);
// Update index
AppContext.getInstance().getIndexingHandler().updateFile(event.getFile());
});
FileUtil.endProcessingFile(event.getFile().getId());
processFile(event, false);
}
/**
* Process the file (create/update).
* Process a file :
* Generate thumbnails
* Extract and save text content
*
* @param event File event
* @param isFileCreated True if the file was just created
*/
private void processFile(FileEvent event) {
private void processFile(FileEvent event, boolean isFileCreated) {
AtomicReference<File> file = new AtomicReference<>();
AtomicReference<User> user = new AtomicReference<>();
// Open a first transaction to get what we need to start the processing
TransactionUtil.handle(() -> {
// Generate thumbnail, extract content
file.set(new FileDao().getActiveById(event.getFileId()));
if (file.get() == null) {
// The file has been deleted since
return;
}
// Get the creating user from the database for its private key
UserDao userDao = new UserDao();
user.set(userDao.getById(file.get().getUserId()));
});
// Process the file outside of a transaction
if (user.get() == null || file.get() == null) {
// The user or file has been deleted
FileUtil.endProcessingFile(event.getFileId());
return;
}
String content = extractContent(event, user.get(), file.get());
// Open a new transaction to save the file content
TransactionUtil.handle(() -> {
// Save the file to database
FileDao fileDao = new FileDao();
File freshFile = fileDao.getActiveById(event.getFileId());
if (freshFile == null) {
// The file has been deleted since the text extraction started, ignore the result
return;
}
freshFile.setContent(content);
fileDao.update(freshFile);
// Update index with the updated file
if (isFileCreated) {
AppContext.getInstance().getIndexingHandler().createFile(freshFile);
} else {
AppContext.getInstance().getIndexingHandler().updateFile(freshFile);
}
});
FileUtil.endProcessingFile(event.getFileId());
}
/**
* Extract text content from a file.
* This is executed outside of a transaction.
*
* @param event File event
* @param user User whom created the file
* @param file Fresh file
* @return Text content
*/
private String extractContent(FileEvent event, User user, File file) {
// Find a format handler
final File file = event.getFile();
FormatHandler formatHandler = FormatHandlerUtil.find(file.getMimeType());
if (formatHandler == null) {
log.info("Format unhandled: " + file.getMimeType());
FileUtil.endProcessingFile(file.getId());
return;
}
// Get the user from the database
UserDao userDao = new UserDao();
User user = userDao.getById(event.getUserId());
if (user == null) {
// The user has been deleted meanwhile
FileUtil.endProcessingFile(file.getId());
return;
return null;
}
// Generate file variations
@@ -132,28 +166,21 @@ public class FileProcessingAsyncListener {
ImageUtil.writeJpeg(thumbnail, outputStream);
}
}
} catch (Exception e) {
log.error("Unable to generate thumbnails", e);
} catch (Throwable e) {
log.error("Unable to generate thumbnails for: " + file, e);
}
// Extract text content from the file
long startTime = System.currentTimeMillis();
String content = null;
log.info("Start extracting content from: " + file);
try {
content = formatHandler.extractContent(event.getLanguage(), event.getUnencryptedFile());
} catch (Exception e) {
log.error("Error extracting content from: " + event.getFile(), e);
} catch (Throwable e) {
log.error("Error extracting content from: " + file, e);
}
log.info(MessageFormat.format("File content extracted in {0}ms", System.currentTimeMillis() - startTime));
log.info(MessageFormat.format("File content extracted in {0}ms: " + file.getId(), System.currentTimeMillis() - startTime));
// Save the file to database
FileDao fileDao = new FileDao();
if (fileDao.getActiveById(file.getId()) == null) {
// The file has been deleted since the text extraction started, ignore the result
return;
}
file.setContent(content);
fileDao.update(file);
return content;
}
}

View File

@@ -36,7 +36,7 @@ public class WebhookAsyncListener {
@Subscribe
@AllowConcurrentEvents
public void on(final DocumentCreatedAsyncEvent event) {
triggerWebhook(WebhookEvent.DOCUMENT_CREATED, event.getDocument().getId());
triggerWebhook(WebhookEvent.DOCUMENT_CREATED, event.getDocumentId());
}
@Subscribe
@@ -54,19 +54,19 @@ public class WebhookAsyncListener {
@Subscribe
@AllowConcurrentEvents
public void on(final FileCreatedAsyncEvent event) {
triggerWebhook(WebhookEvent.FILE_CREATED, event.getFile().getId());
triggerWebhook(WebhookEvent.FILE_CREATED, event.getFileId());
}
@Subscribe
@AllowConcurrentEvents
public void on(final FileUpdatedAsyncEvent event) {
triggerWebhook(WebhookEvent.FILE_UPDATED, event.getFile().getId());
triggerWebhook(WebhookEvent.FILE_UPDATED, event.getFileId());
}
@Subscribe
@AllowConcurrentEvents
public void on(final FileDeletedAsyncEvent event) {
triggerWebhook(WebhookEvent.FILE_DELETED, event.getFile().getId());
triggerWebhook(WebhookEvent.FILE_DELETED, event.getFileId());
}
/**

View File

@@ -5,7 +5,6 @@ import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.UserDao;
import com.sismics.docs.core.event.RebuildIndexAsyncEvent;
import com.sismics.docs.core.listener.async.*;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.service.FileService;
@@ -172,7 +171,8 @@ public class AppContext {
if (EnvironmentUtil.isUnitTest()) {
return new EventBus();
} else {
ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 8,
int threadCount = Math.max(Runtime.getRuntime().availableProcessors() / 2, 2);
ThreadPoolExecutor executor = new ThreadPoolExecutor(threadCount, threadCount,
1L, TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
asyncExecutorList.add(executor);

View File

@@ -49,7 +49,6 @@ public class File implements Loggable {
/**
* OCR-ized content.
*/
@Lob
@Column(name = "FIL_CONTENT_C")
private String content;

View File

@@ -85,7 +85,7 @@ public class FileService extends AbstractScheduledService {
*
* @author bgamard
*/
class TemporaryPathReference extends PhantomReference<Path> {
static class TemporaryPathReference extends PhantomReference<Path> {
String path;
TemporaryPathReference(Path referent, ReferenceQueue<? super Path> q) {
super(referent, q);

View File

@@ -1,16 +1,21 @@
package com.sismics.docs.core.service;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.AbstractScheduledService;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.dao.TagDao;
import com.sismics.docs.core.dao.criteria.TagCriteria;
import com.sismics.docs.core.dao.dto.TagDto;
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
import com.sismics.docs.core.model.jpa.Config;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.Tag;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.docs.core.util.DocumentUtil;
import com.sismics.docs.core.util.FileUtil;
import com.sismics.docs.core.util.TransactionUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.EmailUtil;
import com.sismics.util.context.ThreadLocalContext;
import org.apache.commons.lang.StringUtils;
@@ -19,9 +24,10 @@ import org.slf4j.LoggerFactory;
import javax.mail.*;
import javax.mail.search.FlagTerm;
import java.util.Date;
import java.util.Properties;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Inbox scanning service.
@@ -79,22 +85,25 @@ public class InboxService extends AbstractScheduledService {
lastSyncDate = new Date();
lastSyncMessageCount = 0;
try {
HashMap<String, String> tagsNameToId = getAllTags();
inbox = openInbox();
Message[] messages = inbox.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false));
log.info(messages.length + " messages found");
for (Message message : messages) {
importMessage(message);
importMessage(message, tagsNameToId);
lastSyncMessageCount++;
}
} catch (FolderClosedException e) {
// Ignore this, we will just continue importing on the next cycle
} catch (Exception e) {
log.error("Error synching the inbox", e);
log.error("Error syncing the inbox", e);
lastSyncError = e.getMessage();
} finally {
try {
if (inbox != null) {
inbox.close(false);
// The parameter controls if the messages flagged to be deleted, should actually get deleted.
inbox.close(ConfigUtil.getConfigBooleanValue(ConfigType.INBOX_DELETE_IMPORTED));
inbox.getStore().close();
}
} catch (Exception e) {
@@ -172,7 +181,7 @@ public class InboxService extends AbstractScheduledService {
store.connect(ConfigUtil.getConfigStringValue(ConfigType.INBOX_USERNAME),
ConfigUtil.getConfigStringValue(ConfigType.INBOX_PASSWORD));
Folder inbox = store.getFolder("INBOX");
Folder inbox = store.getFolder(ConfigUtil.getConfigStringValue(ConfigType.INBOX_FOLDER));
inbox.open(Folder.READ_WRITE);
return inbox;
}
@@ -183,7 +192,7 @@ public class InboxService extends AbstractScheduledService {
* @param message Message
* @throws Exception e
*/
private void importMessage(Message message) throws Exception {
private void importMessage(Message message, HashMap<String, String> tags) throws Exception {
log.info("Importing message: " + message.getSubject());
// Parse the mail
@@ -194,12 +203,27 @@ public class InboxService extends AbstractScheduledService {
// Create the document
Document document = new Document();
document.setUserId("admin");
if (mailContent.getSubject() == null) {
document.setTitle("Imported email from EML file");
} else {
document.setTitle(StringUtils.abbreviate(mailContent.getSubject(), 100));
String subject = mailContent.getSubject();
if (subject == null) {
subject = "Imported email from EML file";
}
HashSet<String> tagsFound = new HashSet<>();
if (tags != null) {
Pattern pattern = Pattern.compile("#([^\\s:#]+)");
Matcher matcher = pattern.matcher(subject);
while (matcher.find()) {
if (tags.containsKey(matcher.group(1)) && tags.get(matcher.group(1)) != null) {
tagsFound.add(tags.get(matcher.group(1)));
subject = subject.replaceFirst("#" + matcher.group(1), "");
}
}
log.debug("Tags found: " + String.join(", ", tagsFound));
subject = subject.trim().replaceAll(" +", " ");
}
document.setUserId("admin");
document.setTitle(StringUtils.abbreviate(subject, 100));
document.setDescription(StringUtils.abbreviate(mailContent.getMessage(), 4000));
document.setSubject(StringUtils.abbreviate(mailContent.getSubject(), 500));
document.setFormat("EML");
@@ -220,14 +244,19 @@ public class InboxService extends AbstractScheduledService {
TagDao tagDao = new TagDao();
Tag tag = tagDao.getById(tagId);
if (tag != null) {
tagDao.updateTagList(document.getId(), Sets.newHashSet(tagId));
tagsFound.add(tagId);
}
}
// Update tags
if (!tagsFound.isEmpty()) {
new TagDao().updateTagList(document.getId(), tagsFound);
}
// Raise a document created event
DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent();
documentCreatedAsyncEvent.setUserId("admin");
documentCreatedAsyncEvent.setDocument(document);
documentCreatedAsyncEvent.setDocumentId(document.getId());
ThreadLocalContext.get().addAsyncEvent(documentCreatedAsyncEvent);
// Add files to the document
@@ -235,6 +264,29 @@ public class InboxService extends AbstractScheduledService {
FileUtil.createFile(fileContent.getName(), null, fileContent.getFile(), fileContent.getSize(),
document.getLanguage(), "admin", document.getId());
}
if (ConfigUtil.getConfigBooleanValue(ConfigType.INBOX_DELETE_IMPORTED)) {
message.setFlag(Flags.Flag.DELETED, true);
}
}
/**
* Fetches a HashMap with all tag names as keys and their respective ids as values.
*
* @return HashMap with all tags or null if not enabled
*/
private HashMap<String, String> getAllTags() {
if (!ConfigUtil.getConfigBooleanValue(ConfigType.INBOX_AUTOMATIC_TAGS)) {
return null;
}
TagDao tagDao = new TagDao();
List<TagDto> tags = tagDao.findByCriteria(new TagCriteria().setTargetIdList(null), new SortCriteria(1, true));
HashMap<String, String> tagsNameToId = new HashMap<>();
for (TagDto tagDto : tags) {
tagsNameToId.put(tagDto.getName(), tagDto.getId());
}
return tagsNameToId;
}
public Date getLastSyncDate() {

View File

@@ -50,6 +50,19 @@ public class ConfigUtil {
return Integer.parseInt(value);
}
/**
* Returns the long value of a configuration parameter.
*
* @param configType Type of the configuration parameter
* @return Long value of the configuration parameter
* @throws IllegalStateException Configuration parameter undefined
*/
public static long getConfigLongValue(ConfigType configType) {
String value = getConfigStringValue(configType);
return Long.parseLong(value);
}
/**
* Returns the boolean value of a configuration parameter.
*

View File

@@ -18,6 +18,8 @@ import com.sismics.util.context.ThreadLocalContext;
import com.sismics.util.io.InputStreamReaderThread;
import com.sismics.util.mime.MimeTypeUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
@@ -36,6 +38,11 @@ import java.util.*;
* @author bgamard
*/
public class FileUtil {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(FileUtil.class);
/**
* File ID of files currently being processed.
*/
@@ -76,12 +83,12 @@ public class FileUtil {
/**
* Remove a file from the storage filesystem.
*
* @param file File to delete
* @param fileId ID of file to delete
*/
public static void delete(File file) throws IOException {
Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
Path webFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_web");
Path thumbnailFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_thumb");
public static void delete(String fileId) throws IOException {
Path storedFile = DirectoryUtil.getStorageDirectory().resolve(fileId);
Path webFile = DirectoryUtil.getStorageDirectory().resolve(fileId + "_web");
Path thumbnailFile = DirectoryUtil.getStorageDirectory().resolve(fileId + "_thumb");
if (Files.exists(storedFile)) {
Files.delete(storedFile);
@@ -126,7 +133,7 @@ public class FileUtil {
// Validate global quota
String globalStorageQuotaStr = System.getenv(Constants.GLOBAL_QUOTA_ENV);
if (!Strings.isNullOrEmpty(globalStorageQuotaStr)) {
long globalStorageQuota = Long.valueOf(globalStorageQuotaStr);
long globalStorageQuota = Long.parseLong(globalStorageQuotaStr);
long globalStorageCurrent = userDao.getGlobalStorageCurrent();
if (globalStorageCurrent + fileSize > globalStorageQuota) {
throw new IOException("QuotaReached");
@@ -190,7 +197,7 @@ public class FileUtil {
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
fileCreatedAsyncEvent.setUserId(userId);
fileCreatedAsyncEvent.setLanguage(language);
fileCreatedAsyncEvent.setFile(file);
fileCreatedAsyncEvent.setFileId(file.getId());
fileCreatedAsyncEvent.setUnencryptedFile(unencryptedFile);
ThreadLocalContext.get().addAsyncEvent(fileCreatedAsyncEvent);
@@ -211,6 +218,7 @@ public class FileUtil {
*/
public static void startProcessingFile(String fileId) {
processingFileSet.add(fileId);
log.info("Processing started for file: " + fileId);
}
/**
@@ -220,6 +228,7 @@ public class FileUtil {
*/
public static void endProcessingFile(String fileId) {
processingFileSet.remove(fileId);
log.info("Processing ended for file: " + fileId);
}
/**

View File

@@ -48,7 +48,7 @@ public class ProcessFilesAction implements Action {
FileUpdatedAsyncEvent event = new FileUpdatedAsyncEvent();
event.setUserId("admin");
event.setLanguage(documentDto.getLanguage());
event.setFile(file);
event.setFileId(file.getId());
event.setUnencryptedFile(unencryptedFile);
ThreadLocalContext.get().addAsyncEvent(event);
}

View File

@@ -0,0 +1,131 @@
package com.sismics.docs.core.util.authentication;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.ConfigDao;
import com.sismics.docs.core.dao.UserDao;
import com.sismics.docs.core.model.jpa.Config;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.util.ClasspathScanner;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapConnectionPool;
import org.apache.directory.ldap.client.api.ValidatingPoolableLdapConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
/**
* LDAP authentication handler.
*
* @author bgamard
*/
@ClasspathScanner.Priority(50) // Before the internal database
public class LdapAuthenticationHandler implements AuthenticationHandler {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(LdapAuthenticationHandler.class);
/**
* LDAP connection pool.
*/
private static LdapConnectionPool pool;
/**
* Reset the LDAP pool.
*/
public static void reset() {
if (pool != null) {
try {
pool.close();
} catch (Exception e) {
// NOP
}
}
pool = null;
}
/**
* Initialize the LDAP pool.
*/
private static void init() {
ConfigDao configDao = new ConfigDao();
Config ldapEnabled = configDao.getById(ConfigType.LDAP_ENABLED);
if (pool != null || ldapEnabled == null || !Boolean.parseBoolean(ldapEnabled.getValue())) {
return;
}
LdapConnectionConfig config = new LdapConnectionConfig();
config.setLdapHost(ConfigUtil.getConfigStringValue(ConfigType.LDAP_HOST));
config.setLdapPort(ConfigUtil.getConfigIntegerValue(ConfigType.LDAP_PORT));
config.setName(ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_DN));
config.setCredentials(ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_PASSWORD));
DefaultLdapConnectionFactory factory = new DefaultLdapConnectionFactory(config);
GenericObjectPool.Config poolConfig = new GenericObjectPool.Config();
poolConfig.whenExhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_GROW;
poolConfig.maxWait = 500;
pool = new LdapConnectionPool(new ValidatingPoolableLdapConnectionFactory(factory), poolConfig);
}
@Override
public User authenticate(String username, String password) {
init();
if (pool == null) {
return null;
}
// Fetch and authenticate the user
Entry userEntry;
try {
EntryCursor cursor = pool.getConnection().search(ConfigUtil.getConfigStringValue(ConfigType.LDAP_BASE_DN),
ConfigUtil.getConfigStringValue(ConfigType.LDAP_FILTER).replace("USERNAME", username), SearchScope.SUBTREE);
if (cursor.next()) {
userEntry = cursor.get();
pool.getConnection().bind(userEntry.getDn(), password);
} else {
// User not found
return null;
}
} catch (Exception e) {
log.error("Error authenticating \"" + username + "\" using the LDAP", e);
return null;
}
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
// The user is valid but never authenticated, create the user now
log.info("\"" + username + "\" authenticated for the first time, creating the internal user");
user = new User();
user.setRoleId(Constants.DEFAULT_USER_ROLE);
user.setUsername(username);
user.setPassword(UUID.randomUUID().toString()); // No authentication using the internal database
Attribute mailAttribute = userEntry.get("mail");
if (mailAttribute == null || mailAttribute.get() == null) {
user.setEmail(ConfigUtil.getConfigStringValue(ConfigType.LDAP_DEFAULT_EMAIL));
} else {
Value<?> value = mailAttribute.get();
user.setEmail(value.getString());
}
user.setStorageQuota(ConfigUtil.getConfigLongValue(ConfigType.LDAP_DEFAULT_STORAGE));
try {
userDao.create(user, "admin");
} catch (Exception e) {
log.error("Error while creating the internal user", e);
return null;
}
}
return user;
}
}

View File

@@ -6,6 +6,7 @@ import com.sismics.util.mime.MimeType;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.multipdf.PDFMergerUtility;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.text.PDFTextStripper;
import org.slf4j.Logger;
@@ -60,7 +61,7 @@ public class PdfFormatHandler implements FormatHandler {
for (int pageIndex = 0; pageIndex < pdfDocument.getNumberOfPages(); pageIndex++) {
log.info("OCR page " + (pageIndex + 1) + "/" + pdfDocument.getNumberOfPages() + " of PDF file containing only images");
sb.append(" ");
sb.append(FileUtil.ocrFile(language, renderer.renderImage(pageIndex)));
sb.append(FileUtil.ocrFile(language, renderer.renderImageWithDPI(pageIndex, 300, ImageType.GRAY)));
}
return sb.toString();
} catch (Exception e) {

View File

@@ -46,7 +46,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Timestamp;
import java.util.*;
@@ -252,7 +251,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
" s.SHA_DELETEDATE_D IS NULL group by ac.ACL_SOURCEID_C) s on s.ACL_SOURCEID_C = d.DOC_ID_C " +
" left join (SELECT count(f.FIL_ID_C) count, f.FIL_IDDOC_C " +
" FROM T_FILE f " +
" WHERE f.FIL_DELETEDATE_D IS NULL group by f.FIL_IDDOC_C) f on f.FIL_IDDOC_C = d.DOC_ID_C ");
" WHERE f.FIL_DELETEDATE_D is null group by f.FIL_IDDOC_C) f on f.FIL_IDDOC_C = d.DOC_ID_C ");
sb.append(" left join (select rs.*, rs3.idDocument " +
"from T_ROUTE_STEP rs " +
"join (select r.RTE_IDDOCUMENT_C idDocument, rs.RTP_IDROUTE_C idRoute, min(rs.RTP_ORDER_N) minOrder from T_ROUTE_STEP rs join T_ROUTE r on r.RTE_ID_C = rs.RTP_IDROUTE_C and r.RTE_DELETEDATE_D is null where rs.RTP_DELETEDATE_D is null and rs.RTP_ENDDATE_D is null group by rs.RTP_IDROUTE_C, r.RTE_IDDOCUMENT_C) rs3 on rs.RTP_IDROUTE_C = rs3.idRoute and rs.RTP_ORDER_N = rs3.minOrder " +
@@ -278,7 +277,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
criteriaList.add("d.DOC_ID_C in :documentIdList");
parameterMap.put("documentIdList", documentSearchMap.keySet());
suggestSearchTerms(criteria.getSearch(), suggestionList);
suggestSearchTerms(criteria.getFullSearch(), suggestionList);
}
if (criteria.getCreateDateMin() != null) {
criteriaList.add("d.DOC_CREATEDATE_D >= :createDateMin");
@@ -325,6 +324,11 @@ public class LuceneIndexingHandler implements IndexingHandler {
if (criteria.getShared() != null && criteria.getShared()) {
criteriaList.add("s.count > 0");
}
if (criteria.getMimeType() != null) {
sb.append("left join T_FILE f0 on f0.FIL_IDDOC_C = d.DOC_ID_C and f0.FIL_MIMETYPE_C = :mimeType and f0.FIL_DELETEDATE_D is null");
parameterMap.put("mimeType", criteria.getMimeType());
criteriaList.add("f0.FIL_ID_C is not null");
}
if (criteria.getLanguage() != null) {
criteriaList.add("d.DOC_LANGUAGE_C = :language");
parameterMap.put("language", criteria.getLanguage());
@@ -390,7 +394,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
LuceneDictionary dictionary = new LuceneDictionary(directoryReader, "title");
suggester.build(dictionary);
int lastIndex = search.lastIndexOf(' ');
String suggestQuery = search.substring(lastIndex < 0 ? 0 : lastIndex);
String suggestQuery = search.substring(Math.max(lastIndex, 0));
List<Lookup.LookupResult> lookupResultList = suggester.lookup(suggestQuery, false, 10);
for (Lookup.LookupResult lookupResult : lookupResultList) {
suggestionList.add(lookupResult.key.toString());

View File

@@ -99,11 +99,16 @@ public class EmailUtil {
}
// Port
int port = ConfigUtil.getConfigIntegerValue(ConfigType.SMTP_PORT);
String envPort = System.getenv(Constants.SMTP_PORT_ENV);
if (envPort == null) {
email.setSmtpPort(ConfigUtil.getConfigIntegerValue(ConfigType.SMTP_PORT));
} else {
email.setSmtpPort(Integer.valueOf(envPort));
if (envPort != null) {
port = Integer.valueOf(envPort);
}
email.setSmtpPort(port);
if (port == 465) {
email.setSSLOnConnect(true);
} else if (port == 587) {
email.setStartTLSRequired(true);
}
// Username and password

View File

@@ -1 +1 @@
db.version=24
db.version=27

View File

@@ -41,4 +41,4 @@ insert into T_LOCALE(LOC_ID_C) values('fr');
insert into T_ROLE(ROL_ID_C, ROL_NAME_C, ROL_CREATEDATE_D) values('admin', 'Admin', NOW());
insert into T_ROLE(ROL_ID_C, ROL_NAME_C, ROL_CREATEDATE_D) values('user', 'User', NOW());
insert into T_ROLE_BASE_FUNCTION(RBF_ID_C, RBF_IDROLE_C, RBF_IDBASEFUNCTION_C, RBF_CREATEDATE_D) values('admin_ADMIN', 'admin', 'ADMIN', NOW());
insert into T_USER(USE_ID_C, USE_IDLOCALE_C, USE_IDROLE_C, USE_USERNAME_C, USE_PASSWORD_C, USE_EMAIL_C, USE_THEME_C, USE_FIRSTCONNECTION_B, USE_CREATEDATE_D, USE_PRIVATEKEY_C) values('admin', 'en', 'admin', 'admin', '$2a$05$6Ny3TjrW3aVAL1or2SlcR.fhuDgPKp5jp.P9fBXwVNePgeLqb4i3C', 'admin@localhost', 'default.less', true, NOW(), 'AdminPk');
insert into T_USER(USE_ID_C, USE_IDLOCALE_C, USE_IDROLE_C, USE_USERNAME_C, USE_PASSWORD_C, USE_EMAIL_C, USE_THEME_C, USE_FIRSTCONNECTION_B, USE_CREATEDATE_D, USE_PRIVATEKEY_C) values('admin', 'en', 'admin', 'admin', '$2y$10$xg0EEKVUehutDI1m6qQhVeFz7SMQMl1jQzjf2KkVsR2c7aV2vyyjK', 'admin@localhost', 'default.less', true, NOW(), 'AdminPk');

View File

@@ -0,0 +1,3 @@
insert into T_CONFIG(CFG_ID_C, CFG_VALUE_C) values('INBOX_AUTOMATIC_TAGS', 'false');
insert into T_CONFIG(CFG_ID_C, CFG_VALUE_C) values('INBOX_DELETE_IMPORTED', 'false');
update T_CONFIG set CFG_VALUE_C = '25' where CFG_ID_C = 'DB_VERSION';

View File

@@ -0,0 +1,2 @@
!PGSQL!UPDATE t_file SET fil_content_c = convert_from(loread(lo_open(fil_content_c::int, CAST( x'20000' AS integer)), 999999999), 'UNICODE')::TEXT WHERE fil_content_c IS NOT NULL;
update T_CONFIG set CFG_VALUE_C = '26' where CFG_ID_C = 'DB_VERSION';

View File

@@ -0,0 +1,2 @@
insert into T_CONFIG(CFG_ID_C, CFG_VALUE_C) values('INBOX_FOLDER', 'INBOX');
update T_CONFIG set CFG_VALUE_C = '27' where CFG_ID_C = 'DB_VERSION';

View File

@@ -1,10 +0,0 @@
email.template.password_recovery.subject=Bitte setzen Sie ihr Passwort zur\u00FCck
email.template.password_recovery.hello=Hallo {0}.
email.template.password_recovery.instruction1=Wir haben eine Anfrage zum Zur\u00FCcksetzen Ihres Passworts erhalten.<br/>Wenn Sie keine Hilfe angefordert haben, k\u00F6nnen Sie diese E-Mail einfach ignorieren.
email.template.password_recovery.instruction2=Um Ihr Passwort zur\u00FCckzusetzen, besuchen Sie bitte den folgenden Link:
email.template.password_recovery.click_here=Klicken Sie hier, um Ihr Passwort zur\u00FCckzusetzen
email.template.route_step_validate.subject=Ein Dokument braucht Ihre Aufmerksamkeit
email.template.route_step_validate.hello=Hallo {0}.
email.template.route_step_validate.instruction1=Ihnen wurde ein Workflow-Schritt zugewiesen, der Ihre Aufmerksamkeit erfordert.
email.template.route_step_validate.instruction2=Um das Dokument anzuzeigen und den Workflow zu \u00FCberpr\u00FCfen, besuchen Sie bitte den folgenden Link:
email.no_html.error=Ihr E-Mail-Client unterst\u00FCtzt keine HTML-Nachrichten

View File

@@ -1,10 +0,0 @@
email.template.password_recovery.subject=R\u00E9initialiser votre mot de passe
email.template.password_recovery.hello=Bonjour {0}.
email.template.password_recovery.instruction1=Nous avons re\u00E7u une demande de r\u00E9initialisation de mot de passe.<br/>Si vous n'avez rien demand\u00E9, vous pouvez ignorer cet mail.
email.template.password_recovery.instruction2=Pour r\u00E9initialiser votre mot de passe, cliquez sur le lien ci-dessous :
email.template.password_recovery.click_here=Cliquez ici pour r\u00E9initialiser votre mot de passe.
email.template.route_step_validate.subject=Un document n\u00E9cessite votre attention
email.template.route_step_validate.hello=Bonjour {0}.
email.template.route_step_validate.instruction1=Une \u00E9tape de workflow vous a \u00E9t\u00E9 attribu\u00E9e et n\u00E9cessite votre attention.
email.template.route_step_validate.instruction2=Pour voir le document et valider le workflow, veuillez visiter le lien ci-dessous :
email.no_html.error=Votre client mail ne supporte pas les messages HTML

View File

@@ -1,10 +0,0 @@
email.template.password_recovery.subject=\u8BF7\u91CD\u7F6E\u60A8\u7684\u5BC6\u7801
email.template.password_recovery.hello=\u60A8\u597D {0}.
email.template.password_recovery.instruction1=\u6211\u4EEC\u6536\u5230\u4E86\u4E00\u4E2A\u91CD\u7F6E\u60A8\u7684\u5BC6\u7801\u7684\u8BF7\u6C42\u3002<br/>\u5982\u679C\u60A8\u6CA1\u6709\u53D1\u9001\u8BE5\u8BF7\u6C42\uFF0C\u8BF7\u5FFD\u7565\u6B64\u7535\u5B50\u90AE\u4EF6
email.template.password_recovery.instruction2=\u8981\u91CD\u7F6E\u60A8\u7684\u5BC6\u7801\uFF0C\u8BF7\u8BBF\u95EE\u4EE5\u4E0B\u94FE\u63A5\uFF1A
email.template.password_recovery.click_here=\u8BF7\u70B9\u51FB\u6B64\u5904\u91CD\u7F6E\u60A8\u7684\u5BC6\u7801
email.template.route_step_validate.subject=\u4E00\u4EFD\u6587\u4EF6\u9700\u8981\u4F60\u7684\u5173\u6CE8
email.template.route_step_validate.hello={0}\uFF0C\u60A8\u597D.
email.template.route_step_validate.instruction1=\u5DE5\u4F5C\u6D41\u6B65\u9AA4\u5DF2\u7ECF\u5206\u914D\u7ED9\u60A8\uFF0C\u9700\u8981\u60A8\u7684\u5173\u6CE8\u3002
email.template.route_step_validate.instruction2=\u8981\u67E5\u770B\u6587\u6863\u5E76\u9A8C\u8BC1\u5DE5\u4F5C\u6D41\u7A0B\uFF0C\u8BF7\u8BBF\u95EE\u4EE5\u4E0B\u94FE\u63A5\uFF1A
email.no_html.error=\u60A8\u7684\u7535\u5B50\u90AE\u4EF6\u5BA2\u6237\u7AEF\u4E0D\u652F\u6301HTML\u683C\u5F0F\u90AE\u4EF6

View File

@@ -1,10 +0,0 @@
email.template.password_recovery.subject=\u8ACB\u91CD\u65B0\u8A2D\u7F6E\u60A8\u7684\u5BC6\u78BC
email.template.password_recovery.hello=\u60A8\u597D{0}\uFF01
email.template.password_recovery.instruction1=\u6211\u5011\u6536\u5230\u4E86\u91CD\u7F6E\u5BC6\u78BC\u7684\u8ACB\u6C42\u3002<br/>\u5982\u679C\u60A8\u6C92\u6709\u8ACB\u6C42\u5E6B\u52A9\uFF0C\u8ACB\u5FFD\u7565\u6B64\u96FB\u5B50\u90F5\u4EF6\u3002
email.template.password_recovery.instruction2=\u8981\u91CD\u7F6E\u60A8\u7684\u5BC6\u78BC\uFF0C\u8ACB\u8A2A\u554F\u4EE5\u4E0B\u93C8\u63A5\uFF1A
email.template.password_recovery.click_here=\u9EDE\u64CA\u9019\u88E1\u91CD\u7F6E\u60A8\u7684\u5BC6\u78BC
email.template.route_step_validate.subject=\u4E00\u4EFD\u6587\u4EF6\u9700\u8981\u4F60\u7684\u95DC\u6CE8
email.template.route_step_validate.hello={0}\uFF0C\u60A8\u597D.
email.template.route_step_validate.instruction1=\u5DE5\u4F5C\u6D41\u6B65\u9A5F\u5DF2\u7D93\u5206\u914D\u7D66\u60A8\uFF0C\u9700\u8981\u60A8\u7684\u95DC\u6CE8\u3002
email.template.route_step_validate.instruction2=\u8981\u67E5\u770B\u6587\u6A94\u4E26\u9A57\u8B49\u5DE5\u4F5C\u6D41\u7A0B\uFF0C\u8ACB\u8A2A\u554F\u4EE5\u4E0B\u93C8\u63A5\uFF1A
email.no_html.error=\u60A8\u7684\u96FB\u5B50\u90F5\u4EF6\u5BA2\u6236\u7AEF\u4E0D\u652F\u6301HTML\u683C\u5F0F\u90F5\u4EF6

View File

@@ -0,0 +1,10 @@
email.template.password_recovery.subject=Proszę zresetować swoje hasło
email.template.password_recovery.hello=Witaj {0}.
email.template.password_recovery.instruction1=Otrzymaliśmy żądanie zresetowania twojego hasła.<br/>Jeśli to nie ty potrzebujesz pomocy, moóżesz zignorować ten email.
email.template.password_recovery.instruction2=Aby zresetować swoje hasło, proszę naciśnij link poniżej:
email.template.password_recovery.click_here=Naciśnij, aby zresetować swoje hasło
email.template.route_step_validate.subject=Dokument potrzebuje twojej uwagi
email.template.route_step_validate.hello=Witaj {0}.
email.template.route_step_validate.instruction1=Został Ci przypisany etap przepływu i wymaga Twojej uwagi.
email.template.route_step_validate.instruction2=Aby wyświetlić dokument i zweryfikować przepływ pracy, kliknij poniższy link:
email.no_html.error=Twój klient poczty e-mail nie obsługuje wiadomości HTML

View File

@@ -0,0 +1,27 @@
package com.sismics.util.format;
import com.sismics.docs.core.util.format.PdfFormatHandler;
import org.junit.Assert;
import org.junit.Test;
import java.nio.file.Paths;
/**
* Test of {@link PdfFormatHandler}
*
* @author bgamard
*/
public class TestPdfFormatHandler {
/**
* Test related to https://github.com/sismics/docs/issues/373.
*/
@Test
public void testIssue373() throws Exception {
PdfFormatHandler formatHandler = new PdfFormatHandler();
String content = formatHandler.extractContent("deu", Paths.get(ClassLoader.getSystemResource("file/issue373.pdf").toURI()));
Assert.assertTrue(content.contains("Aufrechterhaltung"));
Assert.assertTrue(content.contains("Außentemperatur"));
Assert.assertTrue(content.contains("Grundumsatzmessungen"));
Assert.assertTrue(content.contains("ermitteln"));
}
}

Binary file not shown.

View File

@@ -6,4 +6,5 @@ log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
log4j.appender.MEMORY.size=1000
log4j.logger.com.sismics=INFO
log4j.logger.org.hibernate=ERROR
log4j.logger.org.hibernate=ERROR
log4j.logger.org.apache.directory=ERROR

16
docs-importer/Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
FROM node:14.2-alpine AS builder
WORKDIR /build
COPY main.js package-lock.json package.json ./
RUN npm install && npm install -g pkg
RUN pkg -t node14-alpine-x64 .
FROM alpine
ENV TEEDY_TAG= TEEDY_ADDTAGS=false TEEDY_LANG=eng TEEDY_URL='http://localhost:8080' TEEDY_USERNAME=username TEEDY_PASSWORD=password TEEDY_COPYFOLDER= TEEDY_FILEFILTER=*
RUN apk add --no-cache \
libc6-compat \
libstdc++
ADD pref /root/.config/preferences/com.sismics.docs.importer.pref
ADD env.sh /
COPY --from=builder /build/teedy-importer ./
CMD ["/bin/ash","-c","/env.sh && /teedy-importer -d"]

View File

@@ -1,35 +1,53 @@
File Importer
=============
# File Importer
This tool can be used to do a single import of files or to periodically scan for files in an input folder.
Downloads
---------
## Downloads
Built binaries for Windows/Linux/MacOSX can be found at <https://github.com/sismics/docs/releases>
Usage
-----
## Usage
```console
./docs-importer-macos (for MacOSX)
./docs-importer-linux (for Linux)
docs-importer-win.exe (for Windows)
```
A wizard will ask you for the import configuration and write it in `~/.config/preferences/com.sismics.docs.importer.pref`
A wizard will ask you for the import configuration and write it in `~/.config/preferences/com.sismics.docs.importer.pref`.
Words following a `#` in the filename will be added as tags to the document, if there is a tag with the same name on the Server.
For the next start, pass the `-d` argument to skip the wizard:
```console
./docs-importer-linux -d
```
Daemon mode
-----------
The daemon mode scan the input directory every 30 seconds for new files. Once a file is found and imported, it is **deleted**.
## Daemon mode
The daemon mode scan the input directory every 30 seconds for new files. Once a file is found and imported, it is **deleted**. You can set a `copyFolder` to copy the file to before deletion.
## Docker
The docker image needs a volume mounted from a previously generated preference file at `/root/.config/preferences/com.sismics.docs.importer.pref`. The container will start the importer in daemon mode. It will look for files in `/import`.
Example usage:
```
docker run --name teedy-import -d -v /path/to/preferencefile:/root/.config/preferences/com.sismics.docs.importer.pref -v /path/to/import/folder:/import sismics/docs-importer:latest
```
### Environment variables
Instead of mounting the preferences file, the options can also be set by setting the environment variables `TEEDY_TAG`, `TEEDY_ADDTAGS`, `TEEDY_LANG`, `TEEDY_COPYFOLDER`, `TEEDY_FILEFILTER`, `TEEDY_URL`, `TEEDY_USERNAME` and `TEEDY_PASSWORD`.
The latter three have to be set for the importer to work. The value of `TEEDY_TAG` has to be set to the UUID of the tag, not the name (The UUID can be found by visiting `baseUrl/api/tag/list` in your browser).
Example usage:
```
docker run --name teedy-import -d -e TEEDY_TAG=2071fdf7-0e26-409d-b53d-f25823a5eb9e -e TEEDY_ADDTAGS=false -e TEEDY_LANG=eng -e TEEDY_URL='http://teedy.example.com:port' -e TEEDY_USERNAME=username -e TEEDY_PASSWORD=superSecretPassword -v /path/to/import/folder:/import sismics/docs-importer:latest
```
## Build from sources
Build from sources
------------------
```console
npm install
npm install -g pkg
pkg .
```
```

11
docs-importer/env.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/ash
file=/root/.config/preferences/com.sismics.docs.importer.pref
sed -i "s/env1/$TEEDY_TAG/g" $file
sed -i "s/env2/$TEEDY_ADDTAGS/g" $file
sed -i "s/env3/$TEEDY_LANG/g" $file
sed -i "s,env4,$TEEDY_URL,g" $file
sed -i "s/env5/$TEEDY_USERNAME/g" $file
sed -i "s/env6/$TEEDY_PASSWORD/g" $file
sed -i "s,env7,$TEEDY_COPYFOLDER,g" $file
sed -i "s,env8,$TEEDY_FILEFILTER,g" $file
echo "Environment variables replaced"

View File

@@ -1,6 +1,7 @@
'use strict';
const recursive = require('recursive-readdir');
const minimatch = require("minimatch");
const ora = require('ora');
const inquirer = require('inquirer');
const preferences = require('preferences');
@@ -10,6 +11,7 @@ const _ = require('underscore');
const request = require('request').defaults({
jar: true
});
const qs = require('querystring');
// Load preferences
const prefs = new preferences('com.sismics.docs.importer',{
@@ -22,7 +24,7 @@ const prefs = new preferences('com.sismics.docs.importer',{
});
// Welcome message
console.log('Teedy Importer 1.0.0, https://teedy.io' +
console.log('Teedy Importer 1.9, https://teedy.io' +
'\n\n' +
'This program let you import files from your system to Teedy' +
'\n');
@@ -141,13 +143,32 @@ const askPath = () => {
recursive(answers.path, function (error, files) {
spinner.succeed(files.length + ' files in this directory');
askTag();
askFileFilter();
});
});
});
});
};
// Ask for the file filter
const askFileFilter = () => {
console.log('');
inquirer.prompt([
{
type: 'input',
name: 'fileFilter',
message: 'What pattern do you want to use to match files? (eg. *.+(pdf|txt|jpg))',
default: prefs.importer.fileFilter || "*"
}
]).then(answers => {
// Save fileFilter
prefs.importer.fileFilter = answers.fileFilter;
askTag();
});
};
// Ask for the tag to add
const askTag = () => {
console.log('');
@@ -176,7 +197,7 @@ const askTag = () => {
{
type: 'list',
name: 'tag',
message: 'Which tag to add on imported documents?',
message: 'Which tag to add to all imported documents?',
default: defaultTagName,
choices: [ 'No tag' ].concat(_.pluck(tags, 'name'))
}
@@ -184,11 +205,109 @@ const askTag = () => {
// Save tag
prefs.importer.tag = answers.tag === 'No tag' ?
'' : _.findWhere(tags, { name: answers.tag }).id;
askDaemon();
askAddTag();
});
});
};
const askAddTag = () => {
console.log('');
inquirer.prompt([
{
type: 'confirm',
name: 'addtags',
message: 'Do you want to add tags from the filename given with # ?',
default: prefs.importer.addtags === true
}
]).then(answers => {
// Save daemon
prefs.importer.addtags = answers.addtags;
// Save all preferences in case the program is sig-killed
askLang();
});
}
const askLang = () => {
console.log('');
// Load tags
const spinner = ora({
text: 'Loading default language',
spinner: 'flips'
}).start();
request.get({
url: prefs.importer.baseUrl + '/api/app',
}, function (error, response, body) {
if (error || !response || response.statusCode !== 200) {
spinner.fail('Connection to Teedy failed: ' + error);
askLang();
return;
}
spinner.succeed('Language loaded');
const defaultLang = prefs.importer.lang ? prefs.importer.lang : JSON.parse(body).default_language;
inquirer.prompt([
{
type: 'input',
name: 'lang',
message: 'Which should be the default language of the document?',
default: defaultLang
}
]).then(answers => {
// Save tag
prefs.importer.lang = answers.lang
askCopyFolder();
});
});
};
const askCopyFolder = () => {
console.log('');
inquirer.prompt([
{
type: 'input',
name: 'copyFolder',
message: 'Enter a path to copy files before they are deleted or leave empty to disable. The path must end with a \'/\' on MacOS and Linux or with a \'\\\' on Windows. Entering \'undefined\' will disable this again after setting the folder.',
default: prefs.importer.copyFolder
}
]).then(answers => {
// Save path
prefs.importer.copyFolder = answers.copyFolder=='undefined' ? '' : answers.copyFolder;
if (prefs.importer.copyFolder) {
// Test path
const spinner = ora({
text: 'Checking copy folder path',
spinner: 'flips'
}).start();
fs.lstat(answers.copyFolder, (error, stats) => {
if (error || !stats.isDirectory()) {
spinner.fail('Please enter a valid directory path');
askCopyFolder();
return;
}
fs.access(answers.copyFolder, fs.W_OK | fs.R_OK, (error) => {
if (error) {
spinner.fail('This directory is not writable');
askCopyFolder();
return;
}
spinner.succeed('Copy folder set!');
askDaemon();
});
});
}
else {askDaemon();}
});
};
// Ask for daemon mode
const askDaemon = () => {
console.log('');
@@ -245,6 +364,8 @@ const start = () => {
// Import the files
const importFiles = (remove, filesImported) => {
recursive(prefs.importer.path, function (error, files) {
files = files.filter(minimatch.filter(prefs.importer.fileFilter || '*', { matchBase: true }));
if (files.length === 0) {
filesImported();
return;
@@ -270,37 +391,94 @@ const importFile = (file, remove, resolve) => {
spinner: 'flips'
}).start();
request.put({
url: prefs.importer.baseUrl + '/api/document',
form: {
title: file.replace(/^.*[\\\/]/, ''),
language: 'eng',
tags: prefs.importer.tag === '' ? undefined : prefs.importer.tag
}
}, function (error, response, body) {
// Remove path of file
let filename = file.replace(/^.*[\\\/]/, '');
// Get Tags given as hashtags from filename
let taglist = filename.match(/#[^\s:#]+/mg);
taglist = taglist ? taglist.map(s => s.substr(1)) : [];
// Get available tags and UUIDs from server
request.get({
url: prefs.importer.baseUrl + '/api/tag/list',
}, function (error, response, body) {
if (error || !response || response.statusCode !== 200) {
spinner.fail('Upload failed for ' + file + ': ' + error);
resolve();
spinner.fail('Error loading tags');
return;
}
let tagsarray = {};
for (let l of JSON.parse(body).tags) {
tagsarray[l.name] = l.id;
}
request.put({
url: prefs.importer.baseUrl + '/api/file',
formData: {
id: JSON.parse(body).id,
file: fs.createReadStream(file)
// Intersect tags from filename with existing tags on server
let foundtags = [];
for (let j of taglist) {
// If the tag is last in the filename it could include a file extension and would not be recognized
if (j.includes('.') && !tagsarray.hasOwnProperty(j) && !foundtags.includes(tagsarray[j])) {
while (j.includes('.') && !tagsarray.hasOwnProperty(j)) {
j = j.replace(/\.[^.]*$/,'');
}
}
}, function (error, response) {
if (tagsarray.hasOwnProperty(j) && !foundtags.includes(tagsarray[j])) {
foundtags.push(tagsarray[j]);
filename = filename.split('#'+j).join('');
}
}
if (prefs.importer.tag !== '' && !foundtags.includes(prefs.importer.tag)){
foundtags.push(prefs.importer.tag);
}
let data = {}
if (prefs.importer.addtags) {
data = {
title: prefs.importer.addtags ? filename : file.replace(/^.*[\\\/]/, '').substring(0, 100),
language: prefs.importer.lang || 'eng',
tags: foundtags
}
}
else {
data = {
title: prefs.importer.addtags ? filename : file.replace(/^.*[\\\/]/, '').substring(0, 100),
language: prefs.importer.lang || 'eng',
tags: prefs.importer.tag === '' ? undefined : prefs.importer.tag
}
}
// Create document
request.put({
url: prefs.importer.baseUrl + '/api/document',
form: qs.stringify(data)
}, function (error, response, body) {
if (error || !response || response.statusCode !== 200) {
spinner.fail('Upload failed for ' + file + ': ' + error);
resolve();
return;
}
spinner.succeed('Upload successful for ' + file);
if (remove) {
fs.unlinkSync(file);
}
resolve();
// Upload file
request.put({
url: prefs.importer.baseUrl + '/api/file',
formData: {
id: JSON.parse(body).id,
file: fs.createReadStream(file)
}
}, function (error, response) {
if (error || !response || response.statusCode !== 200) {
spinner.fail('Upload failed for ' + file + ': ' + error);
resolve();
return;
}
spinner.succeed('Upload successful for ' + file);
if (remove) {
if (prefs.importer.copyFolder) {
fs.copyFileSync(file, prefs.importer.copyFolder + file.replace(/^.*[\\\/]/, ''));
fs.unlinkSync(file);
}
else {fs.unlinkSync(file);}
}
resolve();
});
});
});
};
@@ -312,7 +490,12 @@ if (argv.hasOwnProperty('d')) {
'Username: ' + prefs.importer.username + '\n' +
'Password: ***********\n' +
'Tag: ' + prefs.importer.tag + '\n' +
'Daemon mode: ' + prefs.importer.daemon);
'Add tags given #: ' + prefs.importer.addtags + '\n' +
'Language: ' + prefs.importer.lang + '\n' +
'Daemon mode: ' + prefs.importer.daemon + '\n' +
'Copy folder: ' + prefs.importer.copyFolder + '\n' +
'File filter: ' + prefs.importer.fileFilter
);
start();
} else {
askBaseUrl();

View File

@@ -1,6 +1,6 @@
{
"name": "docs-importer",
"version": "1.5.1",
"name": "teedy-importer",
"version": "1.9.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -9,10 +9,10 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
"requires": {
"co": "4.6.0",
"fast-deep-equal": "1.1.0",
"fast-json-stable-stringify": "2.0.0",
"json-schema-traverse": "0.3.1"
"co": "^4.6.0",
"fast-deep-equal": "^1.0.0",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.3.0"
}
},
"ansi-escapes": {
@@ -30,7 +30,7 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"requires": {
"color-convert": "1.9.1"
"color-convert": "^1.9.0"
}
},
"argparse": {
@@ -38,7 +38,7 @@
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "1.0.3"
"sprintf-js": "~1.0.2"
}
},
"asn1": {
@@ -75,9 +75,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
"integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
"optional": true,
"requires": {
"tweetnacl": "0.14.5"
"tweetnacl": "^0.14.3"
}
},
"boom": {
@@ -85,7 +84,7 @@
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
"requires": {
"hoek": "4.2.1"
"hoek": "4.x.x"
}
},
"brace-expansion": {
@@ -93,7 +92,7 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "1.0.0",
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
@@ -107,9 +106,9 @@
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz",
"integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==",
"requires": {
"ansi-styles": "3.2.0",
"escape-string-regexp": "1.0.5",
"supports-color": "5.2.0"
"ansi-styles": "^3.2.0",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.2.0"
}
},
"chardet": {
@@ -122,7 +121,7 @@
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
"requires": {
"restore-cursor": "2.0.0"
"restore-cursor": "^2.0.0"
}
},
"cli-spinners": {
@@ -150,7 +149,7 @@
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"requires": {
"color-name": "1.1.3"
"color-name": "^1.1.1"
}
},
"color-name": {
@@ -163,7 +162,7 @@
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
"requires": {
"delayed-stream": "1.0.0"
"delayed-stream": "~1.0.0"
}
},
"concat-map": {
@@ -181,7 +180,7 @@
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
"requires": {
"boom": "5.2.0"
"boom": "5.x.x"
},
"dependencies": {
"boom": {
@@ -189,7 +188,7 @@
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
"requires": {
"hoek": "4.2.1"
"hoek": "4.x.x"
}
}
}
@@ -199,7 +198,7 @@
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"requires": {
"assert-plus": "1.0.0"
"assert-plus": "^1.0.0"
}
},
"defaults": {
@@ -207,7 +206,7 @@
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
"requires": {
"clone": "1.0.3"
"clone": "^1.0.2"
}
},
"delayed-stream": {
@@ -219,9 +218,8 @@
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
"integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
"optional": true,
"requires": {
"jsbn": "0.1.1"
"jsbn": "~0.1.0"
}
},
"escape-string-regexp": {
@@ -244,9 +242,9 @@
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz",
"integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==",
"requires": {
"chardet": "0.4.2",
"iconv-lite": "0.4.19",
"tmp": "0.0.33"
"chardet": "^0.4.0",
"iconv-lite": "^0.4.17",
"tmp": "^0.0.33"
}
},
"extsprintf": {
@@ -269,7 +267,7 @@
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
"integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
"requires": {
"escape-string-regexp": "1.0.5"
"escape-string-regexp": "^1.0.5"
}
},
"forever-agent": {
@@ -282,9 +280,9 @@
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"requires": {
"asynckit": "0.4.0",
"asynckit": "^0.4.0",
"combined-stream": "1.0.6",
"mime-types": "2.1.18"
"mime-types": "^2.1.12"
}
},
"getpass": {
@@ -292,7 +290,7 @@
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"requires": {
"assert-plus": "1.0.0"
"assert-plus": "^1.0.0"
}
},
"graceful-fs": {
@@ -310,8 +308,8 @@
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
"requires": {
"ajv": "5.5.2",
"har-schema": "2.0.0"
"ajv": "^5.1.0",
"har-schema": "^2.0.0"
}
},
"has-flag": {
@@ -324,10 +322,10 @@
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
"requires": {
"boom": "4.3.1",
"cryptiles": "3.1.2",
"hoek": "4.2.1",
"sntp": "2.1.0"
"boom": "4.x.x",
"cryptiles": "3.x.x",
"hoek": "4.x.x",
"sntp": "2.x.x"
}
},
"hoek": {
@@ -340,9 +338,9 @@
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"requires": {
"assert-plus": "1.0.0",
"jsprim": "1.4.1",
"sshpk": "1.13.1"
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
}
},
"iconv-lite": {
@@ -360,19 +358,19 @@
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.1.0.tgz",
"integrity": "sha512-kn7N70US1MSZHZHSGJLiZ7iCwwncc7b0gc68YtlX29OjI3Mp0tSVV+snVXpZ1G+ONS3Ac9zd1m6hve2ibLDYfA==",
"requires": {
"ansi-escapes": "3.0.0",
"chalk": "2.3.1",
"cli-cursor": "2.1.0",
"cli-width": "2.2.0",
"external-editor": "2.1.0",
"figures": "2.0.0",
"lodash": "4.17.5",
"ansi-escapes": "^3.0.0",
"chalk": "^2.0.0",
"cli-cursor": "^2.1.0",
"cli-width": "^2.0.0",
"external-editor": "^2.1.0",
"figures": "^2.0.0",
"lodash": "^4.3.0",
"mute-stream": "0.0.7",
"run-async": "2.3.0",
"rxjs": "5.5.6",
"string-width": "2.1.1",
"strip-ansi": "4.0.0",
"through": "2.3.8"
"run-async": "^2.2.0",
"rxjs": "^5.5.2",
"string-width": "^2.1.0",
"strip-ansi": "^4.0.0",
"through": "^2.3.6"
}
},
"is-fullwidth-code-point": {
@@ -396,19 +394,18 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"js-yaml": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
"integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"requires": {
"argparse": "1.0.10",
"esprima": "4.0.0"
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"optional": true
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
},
"json-schema": {
"version": "0.2.3",
@@ -437,16 +434,16 @@
}
},
"lodash": {
"version": "4.17.5",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz",
"integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw=="
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"log-symbols": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
"requires": {
"chalk": "2.3.1"
"chalk": "^2.0.1"
}
},
"mime-db": {
@@ -459,7 +456,7 @@
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
"requires": {
"mime-db": "1.33.0"
"mime-db": "~1.33.0"
}
},
"mimic-fn": {
@@ -472,27 +469,20 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "1.1.11"
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "0.0.8"
},
"dependencies": {
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
}
"minimist": "^1.2.5"
}
},
"mute-stream": {
@@ -510,7 +500,7 @@
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
"requires": {
"mimic-fn": "1.2.0"
"mimic-fn": "^1.0.0"
}
},
"ora": {
@@ -518,12 +508,12 @@
"resolved": "https://registry.npmjs.org/ora/-/ora-2.0.0.tgz",
"integrity": "sha512-g+IR0nMUXq1k4nE3gkENbN4wkF0XsVZFyxznTF6CdmwQ9qeTGONGpSR9LM5//1l0TVvJoJF3MkMtJp6slUsWFg==",
"requires": {
"chalk": "2.3.1",
"cli-cursor": "2.1.0",
"cli-spinners": "1.1.0",
"log-symbols": "2.2.0",
"strip-ansi": "4.0.0",
"wcwidth": "1.0.1"
"chalk": "^2.3.1",
"cli-cursor": "^2.1.0",
"cli-spinners": "^1.1.0",
"log-symbols": "^2.2.0",
"strip-ansi": "^4.0.0",
"wcwidth": "^1.0.1"
}
},
"os-homedir": {
@@ -546,11 +536,11 @@
"resolved": "https://registry.npmjs.org/preferences/-/preferences-1.0.2.tgz",
"integrity": "sha512-cRjA8Galk1HDDBOKjx6DhTwfy5+FVZtH7ogg6rgTLX8Ak4wi55RaS4uRztJuVPd+md1jZo99bH/h1Q9bQQK8bg==",
"requires": {
"graceful-fs": "4.1.11",
"js-yaml": "3.10.0",
"mkdirp": "0.5.1",
"os-homedir": "1.0.2",
"write-file-atomic": "1.3.4"
"graceful-fs": "^4.1.2",
"js-yaml": "^3.10.0",
"mkdirp": "^0.5.1",
"os-homedir": "^1.0.1",
"write-file-atomic": "^1.1.3"
}
},
"punycode": {
@@ -559,9 +549,9 @@
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
},
"qs": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
"integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ=="
},
"recursive-readdir": {
"version": "2.2.2",
@@ -576,28 +566,35 @@
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
"integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
"requires": {
"aws-sign2": "0.7.0",
"aws4": "1.6.0",
"caseless": "0.12.0",
"combined-stream": "1.0.6",
"extend": "3.0.1",
"forever-agent": "0.6.1",
"form-data": "2.3.2",
"har-validator": "5.0.3",
"hawk": "6.0.2",
"http-signature": "1.2.0",
"is-typedarray": "1.0.0",
"isstream": "0.1.2",
"json-stringify-safe": "5.0.1",
"mime-types": "2.1.18",
"oauth-sign": "0.8.2",
"performance-now": "2.1.0",
"qs": "6.5.1",
"safe-buffer": "5.1.1",
"stringstream": "0.0.5",
"tough-cookie": "2.3.4",
"tunnel-agent": "0.6.0",
"uuid": "3.2.1"
"aws-sign2": "~0.7.0",
"aws4": "^1.6.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.5",
"extend": "~3.0.1",
"forever-agent": "~0.6.1",
"form-data": "~2.3.1",
"har-validator": "~5.0.3",
"hawk": "~6.0.2",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.17",
"oauth-sign": "~0.8.2",
"performance-now": "^2.1.0",
"qs": "~6.5.1",
"safe-buffer": "^5.1.1",
"stringstream": "~0.0.5",
"tough-cookie": "~2.3.3",
"tunnel-agent": "^0.6.0",
"uuid": "^3.1.0"
},
"dependencies": {
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
}
}
},
"restore-cursor": {
@@ -605,8 +602,8 @@
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
"requires": {
"onetime": "2.0.1",
"signal-exit": "3.0.2"
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
}
},
"run-async": {
@@ -614,7 +611,7 @@
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
"integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
"requires": {
"is-promise": "2.1.0"
"is-promise": "^2.1.0"
}
},
"rxjs": {
@@ -630,6 +627,11 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
@@ -645,7 +647,7 @@
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
"integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==",
"requires": {
"hoek": "4.2.1"
"hoek": "4.x.x"
}
},
"sprintf-js": {
@@ -654,18 +656,19 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sshpk": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
"integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
"requires": {
"asn1": "0.2.3",
"assert-plus": "1.0.0",
"bcrypt-pbkdf": "1.0.1",
"dashdash": "1.14.1",
"ecc-jsbn": "0.1.1",
"getpass": "0.1.7",
"jsbn": "0.1.1",
"tweetnacl": "0.14.5"
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
}
},
"string-width": {
@@ -673,8 +676,8 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"requires": {
"is-fullwidth-code-point": "2.0.0",
"strip-ansi": "4.0.0"
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
}
},
"stringstream": {
@@ -687,7 +690,7 @@
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"requires": {
"ansi-regex": "3.0.0"
"ansi-regex": "^3.0.0"
}
},
"supports-color": {
@@ -695,7 +698,7 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",
"integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==",
"requires": {
"has-flag": "3.0.0"
"has-flag": "^3.0.0"
}
},
"symbol-observable": {
@@ -713,7 +716,7 @@
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"requires": {
"os-tmpdir": "1.0.2"
"os-tmpdir": "~1.0.2"
}
},
"tough-cookie": {
@@ -721,7 +724,7 @@
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
"integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
"requires": {
"punycode": "1.4.1"
"punycode": "^1.4.1"
}
},
"tunnel-agent": {
@@ -729,14 +732,13 @@
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"requires": {
"safe-buffer": "5.1.1"
"safe-buffer": "^5.0.1"
}
},
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"optional": true
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"underscore": {
"version": "1.8.3",
@@ -753,9 +755,9 @@
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"requires": {
"assert-plus": "1.0.0",
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "1.3.0"
"extsprintf": "^1.2.0"
}
},
"wcwidth": {
@@ -763,7 +765,7 @@
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
"requires": {
"defaults": "1.0.3"
"defaults": "^1.0.3"
}
},
"write-file-atomic": {
@@ -771,9 +773,9 @@
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz",
"integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=",
"requires": {
"graceful-fs": "4.1.11",
"imurmurhash": "0.1.4",
"slide": "1.1.6"
"graceful-fs": "^4.1.11",
"imurmurhash": "^0.1.4",
"slide": "^1.1.5"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "teedy-importer",
"version": "1.5.1",
"version": "1.9.0",
"description": "Import files to Teedy",
"bin": "main.js",
"scripts": {
@@ -11,6 +11,9 @@
"url": "git+https://github.com/sismics/docs.git"
},
"author": "Benjamin Gamard",
"contributors": [
"Cornelius Hoffmann <coding@hoffmn.de>"
],
"license": "GPL-2.0",
"bugs": {
"url": "https://github.com/sismics/docs/issues"
@@ -18,10 +21,12 @@
"homepage": "https://github.com/sismics/docs#readme",
"dependencies": {
"inquirer": "^5.1.0",
"minimist": "^1.2.0",
"minimist": "^1.2.5",
"ora": "^2.0.0",
"preferences": "^1.0.2",
"qs": "^6.9.4",
"recursive-readdir": "^2.2.2",
"minimatch": "^3.0.4",
"request": "^2.83.0",
"underscore": "^1.8.3"
}

11
docs-importer/pref Normal file
View File

@@ -0,0 +1,11 @@
importer:
daemon: true
path: import
tag: 'env1'
addtags: 'env2'
lang: 'env3'
baseUrl: 'env4'
username: 'env5'
password: 'env6'
copyFolder: 'env7'
fileFilter: 'env8'

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.7</version>
<version>1.9</version>
<relativePath>..</relativePath>
</parent>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.7</version>
<version>1.9</version>
<relativePath>..</relativePath>
</parent>
@@ -68,6 +68,11 @@
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>

View File

@@ -111,7 +111,18 @@ public class ValidationUtil {
public static void validateHexColor(String s, String name, boolean nullable) throws ClientException {
ValidationUtil.validateLength(s, name, 7, 7, nullable);
}
/**
* Validate a tag name.
*
* @param name Name of the tag
*/
public static void validateTagName(String name) throws ClientException {
if (name.contains(" ") || name.contains(":")) {
throw new ClientException("IllegalTagName", "Spaces and colons are not allowed in tag name");
}
}
/**
* Validates that the provided string matches an URL with HTTP or HTTPS scheme.
*

View File

@@ -11,6 +11,7 @@ import org.apache.log4j.PatternLayout;
import org.apache.log4j.RollingFileAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
@@ -57,6 +58,8 @@ public class RequestContextFilter implements Filter {
fileAppender.setMaxBackupIndex(5);
fileAppender.activateOptions();
org.apache.log4j.Logger.getRootLogger().addAppender(fileAppender);
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
// Initialize the application context
TransactionUtil.handle(AppContext::getInstance);

View File

@@ -5,6 +5,7 @@ import com.sismics.util.filter.TokenBasedSecurityFilter;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart;
import org.junit.Assert;
import javax.json.JsonObject;
import javax.ws.rs.client.Entity;
@@ -113,7 +114,8 @@ public class ClientUtil {
.param("username", username)
.param("password", password)
.param("remember", remember.toString())));
Assert.assertEquals(200, response.getStatus());
return getAuthenticationCookie(response);
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.7</version>
<version>1.9</version>
<relativePath>..</relativePath>
</parent>
@@ -26,25 +26,6 @@
<artifactId>docs-web-common</artifactId>
</dependency>
<!-- JDK 11 JAXB dependencies -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<!-- Dependencies to Jersey -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
@@ -157,7 +138,7 @@
<artifactId>greenmail</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=24
db.version=27

View File

@@ -6,3 +6,6 @@ log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
log4j.appender.MEMORY.size=1000
log4j.logger.com.sismics=DEBUG
log4j.logger.org.apache.pdfbox=ERROR
log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR
log4j.logger.org.apache.directory=ERROR

View File

@@ -14,6 +14,7 @@ import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.service.InboxService;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.docs.core.util.DirectoryUtil;
import com.sismics.docs.core.util.authentication.LdapAuthenticationHandler;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.rest.constant.BaseFunction;
@@ -311,6 +312,7 @@ public class AppResource extends BaseResource {
* @apiSuccess {String} port IMAP port
* @apiSuccess {String} username IMAP username
* @apiSuccess {String} password IMAP password
* @apiSuccess {String} folder IMAP folder
* @apiSuccess {String} tag Tag for created documents
* @apiError (client) ForbiddenError Access denied
* @apiPermission admin
@@ -328,14 +330,19 @@ public class AppResource extends BaseResource {
ConfigDao configDao = new ConfigDao();
Boolean enabled = ConfigUtil.getConfigBooleanValue(ConfigType.INBOX_ENABLED);
Boolean autoTags = ConfigUtil.getConfigBooleanValue(ConfigType.INBOX_AUTOMATIC_TAGS);
Boolean deleteImported = ConfigUtil.getConfigBooleanValue(ConfigType.INBOX_DELETE_IMPORTED);
Config hostnameConfig = configDao.getById(ConfigType.INBOX_HOSTNAME);
Config portConfig = configDao.getById(ConfigType.INBOX_PORT);
Config usernameConfig = configDao.getById(ConfigType.INBOX_USERNAME);
Config passwordConfig = configDao.getById(ConfigType.INBOX_PASSWORD);
Config folderConfig = configDao.getById(ConfigType.INBOX_FOLDER);
Config tagConfig = configDao.getById(ConfigType.INBOX_TAG);
JsonObjectBuilder response = Json.createObjectBuilder();
response.add("enabled", enabled);
response.add("autoTagsEnabled", autoTags);
response.add("deleteImported", deleteImported);
if (hostnameConfig == null) {
response.addNull("hostname");
} else {
@@ -356,6 +363,11 @@ public class AppResource extends BaseResource {
} else {
response.add("password", passwordConfig.getValue());
}
if (folderConfig == null) {
response.addNull("folder");
} else {
response.add("folder", folderConfig.getValue());
}
if (tagConfig == null) {
response.addNull("tag");
} else {
@@ -388,6 +400,7 @@ public class AppResource extends BaseResource {
* @apiParam {Integer} port IMAP port
* @apiParam {String} username IMAP username
* @apiParam {String} password IMAP password
* @apiParam {String} folder IMAP folder
* @apiParam {String} tag Tag for created documents
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
@@ -399,16 +412,20 @@ public class AppResource extends BaseResource {
* @param portStr IMAP port
* @param username IMAP username
* @param password IMAP password
* @param folder IMAP folder
* @param tag Tag for created documents
* @return Response
*/
@POST
@Path("config_inbox")
public Response configInbox(@FormParam("enabled") Boolean enabled,
@FormParam("autoTagsEnabled") Boolean autoTagsEnabled,
@FormParam("deleteImported") Boolean deleteImported,
@FormParam("hostname") String hostname,
@FormParam("port") String portStr,
@FormParam("username") String username,
@FormParam("password") String password,
@FormParam("folder") String folder,
@FormParam("tag") String tag) {
if (!authenticate()) {
throw new ForbiddenClientException();
@@ -422,6 +439,8 @@ public class AppResource extends BaseResource {
// Just update the changed configuration
ConfigDao configDao = new ConfigDao();
configDao.update(ConfigType.INBOX_ENABLED, enabled.toString());
configDao.update(ConfigType.INBOX_AUTOMATIC_TAGS, autoTagsEnabled.toString());
configDao.update(ConfigType.INBOX_DELETE_IMPORTED, deleteImported.toString());
if (!Strings.isNullOrEmpty(hostname)) {
configDao.update(ConfigType.INBOX_HOSTNAME, hostname);
}
@@ -434,6 +453,9 @@ public class AppResource extends BaseResource {
if (!Strings.isNullOrEmpty(password)) {
configDao.update(ConfigType.INBOX_PASSWORD, password);
}
if (!Strings.isNullOrEmpty(folder)) {
configDao.update(ConfigType.INBOX_FOLDER, folder);
}
if (!Strings.isNullOrEmpty(tag)) {
configDao.update(ConfigType.INBOX_TAG, tag);
}
@@ -688,4 +710,138 @@ public class AppResource extends BaseResource {
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Get the LDAP authentication configuration.
*
* @api {get} /app/config_ldap Get the LDAP authentication configuration
* @apiName GetAppConfigLdap
* @apiGroup App
* @apiSuccess {Boolean} enabled LDAP authentication enabled
* @apiSuccess {String} host LDAP server host
* @apiSuccess {Integer} port LDAP server port
* @apiSuccess {String} admin_dn Admin DN
* @apiSuccess {String} admin_password Admin password
* @apiSuccess {String} base_dn Base DN
* @apiSuccess {String} filter LDAP filter
* @apiSuccess {String} default_email LDAP default email
* @apiSuccess {Integer} default_storage LDAP default storage
* @apiError (client) ForbiddenError Access denied
* @apiPermission admin
* @apiVersion 1.9.0
*
* @return Response
*/
@GET
@Path("config_ldap")
public Response getConfigLdap() {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
ConfigDao configDao = new ConfigDao();
Config enabled = configDao.getById(ConfigType.LDAP_ENABLED);
JsonObjectBuilder response = Json.createObjectBuilder();
if (enabled != null && Boolean.parseBoolean(enabled.getValue())) {
// LDAP enabled
response.add("enabled", true)
.add("host", ConfigUtil.getConfigStringValue(ConfigType.LDAP_HOST))
.add("port", ConfigUtil.getConfigIntegerValue(ConfigType.LDAP_PORT))
.add("admin_dn", ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_DN))
.add("admin_password", ConfigUtil.getConfigStringValue(ConfigType.LDAP_ADMIN_PASSWORD))
.add("base_dn", ConfigUtil.getConfigStringValue(ConfigType.LDAP_BASE_DN))
.add("filter", ConfigUtil.getConfigStringValue(ConfigType.LDAP_FILTER))
.add("default_email", ConfigUtil.getConfigStringValue(ConfigType.LDAP_DEFAULT_EMAIL))
.add("default_storage", ConfigUtil.getConfigLongValue(ConfigType.LDAP_DEFAULT_STORAGE));
} else {
// LDAP disabled
response.add("enabled", false);
}
return Response.ok().entity(response.build()).build();
}
/**
* Configure the LDAP authentication.
*
* @api {post} /app/config_ldap Configure the LDAP authentication
* @apiName PostAppConfigLdap
* @apiGroup App
* @apiParam {Boolean} enabled LDAP authentication enabled
* @apiParam {String} host LDAP server host
* @apiParam {Integer} port LDAP server port
* @apiParam {String} admin_dn Admin DN
* @apiParam {String} admin_password Admin password
* @apiParam {String} base_dn Base DN
* @apiParam {String} filter LDAP filter
* @apiParam {String} default_email LDAP default email
* @apiParam {Integer} default_storage LDAP default storage
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiPermission admin
* @apiVersion 1.9.0
*
* @param enabled LDAP authentication enabled
* @param host LDAP server host
* @param portStr LDAP server port
* @param adminDn Admin DN
* @param adminPassword Admin password
* @param baseDn Base DN
* @param filter LDAP filter
* @param defaultEmail LDAP default email
* @param defaultStorageStr LDAP default storage
* @return Response
*/
@POST
@Path("config_ldap")
public Response configLdap(@FormParam("enabled") Boolean enabled,
@FormParam("host") String host,
@FormParam("port") String portStr,
@FormParam("admin_dn") String adminDn,
@FormParam("admin_password") String adminPassword,
@FormParam("base_dn") String baseDn,
@FormParam("filter") String filter,
@FormParam("default_email") String defaultEmail,
@FormParam("default_storage") String defaultStorageStr) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
ConfigDao configDao = new ConfigDao();
if (enabled != null && enabled) {
// LDAP enabled, validate everything
ValidationUtil.validateLength(host, "host", 1, 250);
ValidationUtil.validateInteger(portStr, "port");
ValidationUtil.validateLength(adminDn, "admin_dn", 1, 250);
ValidationUtil.validateLength(adminPassword, "admin_password", 1, 250);
ValidationUtil.validateLength(baseDn, "base_dn", 1, 250);
ValidationUtil.validateLength(filter, "filter", 1, 250);
if (!filter.contains("USERNAME")) {
throw new ClientException("ValidationError", "'filter' must contains 'USERNAME'");
}
ValidationUtil.validateLength(defaultEmail, "default_email", 1, 250);
ValidationUtil.validateLong(defaultStorageStr, "default_storage");
configDao.update(ConfigType.LDAP_ENABLED, Boolean.TRUE.toString());
configDao.update(ConfigType.LDAP_HOST, host);
configDao.update(ConfigType.LDAP_PORT, portStr);
configDao.update(ConfigType.LDAP_ADMIN_DN, adminDn);
configDao.update(ConfigType.LDAP_ADMIN_PASSWORD, adminPassword);
configDao.update(ConfigType.LDAP_BASE_DN, baseDn);
configDao.update(ConfigType.LDAP_FILTER, filter);
configDao.update(ConfigType.LDAP_DEFAULT_EMAIL, defaultEmail);
configDao.update(ConfigType.LDAP_DEFAULT_STORAGE, defaultStorageStr);
} else {
// LDAP disabled
configDao.update(ConfigType.LDAP_ENABLED, Boolean.FALSE.toString());
}
// Reset the LDAP pool to reconnect with the new configuration
LdapAuthenticationHandler.reset();
return Response.ok().build();
}
}

View File

@@ -104,6 +104,7 @@ public class DocumentResource extends BaseResource {
* @apiSuccess {String="READ","WRITE"} inherited_acls.perm Permission
* @apiSuccess {String} inherited_acls.source_id Source ID
* @apiSuccess {String} inherited_acls.source_name Source name
* @apiSuccess {String} inherited_acls.source_color The color of the Source
* @apiSuccess {String} inherited_acls.id ID
* @apiSuccess {String} inherited_acls.name Target name
* @apiSuccess {String="USER","GROUP","SHARE"} inherited_acls.type Target type
@@ -196,6 +197,7 @@ public class DocumentResource extends BaseResource {
.add("perm", aclDto.getPerm().name())
.add("source_id", tagDto.getId())
.add("source_name", tagDto.getName())
.add("source_color", tagDto.getColor())
.add("id", aclDto.getTargetId())
.add("name", JsonUtil.nullable(aclDto.getTargetName()))
.add("type", aclDto.getTargetType()));
@@ -455,8 +457,8 @@ public class DocumentResource extends BaseResource {
for (String criteria : criteriaList) {
String[] params = criteria.split(":");
if (params.length != 2 || Strings.isNullOrEmpty(params[0]) || Strings.isNullOrEmpty(params[1])) {
// This is not a special criteria
query.add(criteria);
// This is not a special criteria, do a fulltext search on it
fullQuery.add(criteria);
continue;
}
@@ -569,6 +571,10 @@ public class DocumentResource extends BaseResource {
documentCriteria.setLanguage(UUID.randomUUID().toString());
}
break;
case "mime":
// New mime type criteria
documentCriteria.setMimeType(params[1]);
break;
case "by":
// New creator criteria
User user = userDao.getActiveByUsername(params[1]);
@@ -584,12 +590,16 @@ public class DocumentResource extends BaseResource {
// New shared state criteria
documentCriteria.setActiveRoute(params[1].equals("me"));
break;
case "simple":
// New simple search criteria
query.add(params[1]);
break;
case "full":
// New full content search criteria
// New fulltext search criteria
fullQuery.add(params[1]);
break;
default:
query.add(criteria);
fullQuery.add(criteria);
break;
}
}
@@ -723,7 +733,7 @@ public class DocumentResource extends BaseResource {
// Raise a document created event
DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent();
documentCreatedAsyncEvent.setUserId(principal.getId());
documentCreatedAsyncEvent.setDocument(document);
documentCreatedAsyncEvent.setDocumentId(document.getId());
ThreadLocalContext.get().addAsyncEvent(documentCreatedAsyncEvent);
JsonObjectBuilder response = Json.createObjectBuilder()
@@ -940,7 +950,7 @@ public class DocumentResource extends BaseResource {
// Raise a document created event
DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent();
documentCreatedAsyncEvent.setUserId(principal.getId());
documentCreatedAsyncEvent.setDocument(document);
documentCreatedAsyncEvent.setDocumentId(document.getId());
ThreadLocalContext.get().addAsyncEvent(documentCreatedAsyncEvent);
// Add files to the document
@@ -1009,7 +1019,7 @@ public class DocumentResource extends BaseResource {
// Raise file deleted event
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
fileDeletedAsyncEvent.setUserId(principal.getId());
fileDeletedAsyncEvent.setFile(file);
fileDeletedAsyncEvent.setFileId(file.getId());
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
}

View File

@@ -202,7 +202,7 @@ public class FileResource extends BaseResource {
FileUpdatedAsyncEvent fileUpdatedAsyncEvent = new FileUpdatedAsyncEvent();
fileUpdatedAsyncEvent.setUserId(principal.getId());
fileUpdatedAsyncEvent.setLanguage(documentDto.getLanguage());
fileUpdatedAsyncEvent.setFile(file);
fileUpdatedAsyncEvent.setFileId(file.getId());
fileUpdatedAsyncEvent.setUnencryptedFile(unencryptedFile);
ThreadLocalContext.get().addAsyncEvent(fileUpdatedAsyncEvent);
@@ -310,7 +310,7 @@ public class FileResource extends BaseResource {
FileUpdatedAsyncEvent event = new FileUpdatedAsyncEvent();
event.setUserId(principal.getId());
event.setLanguage(documentDto.getLanguage());
event.setFile(file);
event.setFileId(file.getId());
event.setUnencryptedFile(unencryptedFile);
ThreadLocalContext.get().addAsyncEvent(event);
} catch (Exception e) {
@@ -548,7 +548,7 @@ public class FileResource extends BaseResource {
// Raise a new file deleted event
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
fileDeletedAsyncEvent.setUserId(principal.getId());
fileDeletedAsyncEvent.setFile(file);
fileDeletedAsyncEvent.setFileId(file.getId());
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
if (file.getDocumentId() != null) {
@@ -608,7 +608,7 @@ public class FileResource extends BaseResource {
if (size != null) {
if (size.equals("content")) {
return Response.ok(Strings.nullToEmpty(file.getContent()))
.header(HttpHeaders.CONTENT_TYPE, "text/plain")
.header(HttpHeaders.CONTENT_TYPE, "text/plain; charset=utf-8")
.build();
}

View File

@@ -209,7 +209,9 @@ public class RouteResource extends BaseResource {
routeStepDao.endRouteStep(routeStepDto.getId(), routeStepTransition, comment, principal.getId());
RouteStepDto newRouteStep = routeStepDao.getCurrentStep(documentId);
RoutingUtil.updateAcl(documentId, newRouteStep, routeStepDto, principal.getId());
RoutingUtil.sendRouteStepEmail(documentId, routeStepDto);
if (newRouteStep != null) {
RoutingUtil.sendRouteStepEmail(documentId, newRouteStep);
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("readable", aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(null)));

View File

@@ -55,7 +55,7 @@ public class ShareResource extends BaseResource {
public Response add(
@FormParam("id") String documentId,
@FormParam("name") String name) {
if (!authenticate()) {
if (!authenticate() || principal.isGuest()) {
throw new ForbiddenClientException();
}
@@ -119,7 +119,7 @@ public class ShareResource extends BaseResource {
@Path("{id: [a-z0-9\\-]+}")
public Response delete(
@PathParam("id") String id) {
if (!authenticate()) {
if (!authenticate() || principal.isGuest()) {
throw new ForbiddenClientException();
}

View File

@@ -155,7 +155,7 @@ public class TagResource extends BaseResource {
* @apiSuccess {String} id Tag ID
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiError (client) SpacesNotAllowed Spaces are not allowed in tag name
* @apiError (client) IllegalTagName Spaces and colons are not allowed in tag name
* @apiError (client) ParentNotFound Parent not found
* @apiPermission user
* @apiVersion 1.5.0
@@ -177,12 +177,8 @@ public class TagResource extends BaseResource {
// Validate input data
name = ValidationUtil.validateLength(name, "name", 1, 36, false);
ValidationUtil.validateHexColor(color, "color", true);
// Don't allow spaces
if (name.contains(" ")) {
throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name");
}
ValidationUtil.validateTagName(name);
// Check the parent
if (StringUtils.isEmpty(parentId)) {
parentId = null;
@@ -237,7 +233,7 @@ public class TagResource extends BaseResource {
* @apiSuccess {String} id Tag ID
* @apiError (client) ForbiddenError Access denied
* @apiError (client) ValidationError Validation error
* @apiError (client) SpacesNotAllowed Spaces are not allowed in tag name
* @apiError (client) IllegalTagName Spaces and colons are not allowed in tag name
* @apiError (client) ParentNotFound Parent not found
* @apiError (client) CircularReference Circular reference in parent tag
* @apiError (client) NotFound Tag not found
@@ -263,12 +259,8 @@ public class TagResource extends BaseResource {
// Validate input data
name = ValidationUtil.validateLength(name, "name", 1, 36, true);
ValidationUtil.validateHexColor(color, "color", true);
// Don't allow spaces
if (name.contains(" ")) {
throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name");
}
ValidationUtil.validateTagName(name);
// Check permission
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) {

View File

@@ -366,7 +366,7 @@ public class UserResource extends BaseResource {
AuthenticationToken authenticationToken = new AuthenticationToken()
.setUserId(user.getId())
.setLongLasted(longLasted)
.setIp(ip)
.setIp(StringUtils.abbreviate(ip, 45))
.setUserAgent(StringUtils.abbreviate(request.getHeader("user-agent"), 1000));
String token = authenticationTokenDao.create(authenticationToken);
@@ -482,7 +482,7 @@ public class UserResource extends BaseResource {
for (File file : fileList) {
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
fileDeletedAsyncEvent.setUserId(principal.getId());
fileDeletedAsyncEvent.setFile(file);
fileDeletedAsyncEvent.setFileId(file.getId());
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
}
@@ -564,7 +564,7 @@ public class UserResource extends BaseResource {
for (File file : fileList) {
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
fileDeletedAsyncEvent.setUserId(principal.getId());
fileDeletedAsyncEvent.setFile(file);
fileDeletedAsyncEvent.setFileId(file.getId());
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
}

File diff suppressed because it is too large Load Diff

View File

@@ -244,6 +244,15 @@ angular.module('docs',
}
}
})
.state('settings.ldap', {
url: '/ldap',
views: {
'settings': {
templateUrl: 'partial/docs/settings.ldap.html',
controller: 'SettingsLdap'
}
}
})
.state('document', {
url: '/document',
abstract: true,
@@ -347,7 +356,7 @@ angular.module('docs',
}
})
.state('login', {
url: '/login',
url: '/login?redirectState&redirectParams',
views: {
'page': {
templateUrl: 'partial/docs/login.html',
@@ -420,12 +429,15 @@ angular.module('docs',
prefix: 'locale/',
suffix: '.json?@build.date@'
})
.registerAvailableLanguageKeys(['en', 'es', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
'ru_*': 'ru',
.registerAvailableLanguageKeys(['en', 'es', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW'], {
'en_*': 'en',
'es_*': 'es',
'fr_*': 'fr',
'de_*': 'de',
'el_*': 'el',
'ru_*': 'ru',
'it_*': 'it',
'pl_*': 'pl',
'*': 'en'
})
.fallbackLanguage('en');
@@ -436,6 +448,9 @@ angular.module('docs',
} else {
// Or else determine the language based on the user's browser
$translateProvider.determinePreferredLanguage();
if (!$translateProvider.use()) {
$translateProvider.use('en');
}
}
// Configuring Timago
@@ -520,7 +535,13 @@ angular.module('docs',
{ key: 'kor', label: '한국어' },
{ key: 'nld', label: 'Nederlands' },
{ key: 'tur', label: 'Türkçe' },
{ key: 'heb', label: 'עברית' }
{ key: 'heb', label: 'עברית' },
{ key: 'hun', label: 'Magyar' },
{ key: 'fin', label: 'Suomi' },
{ key: 'swe', label: 'Svenska' },
{ key: 'lav', label: 'Latviešu' },
{ key: 'dan', label: 'Dansk' },
{ key: 'nor', label: 'Norsk' }
];
})
/**

View File

@@ -3,7 +3,7 @@
/**
* Login controller.
*/
angular.module('docs').controller('Login', function(Restangular, $scope, $rootScope, $state, $dialog, User, $translate, $uibModal) {
angular.module('docs').controller('Login', function(Restangular, $scope, $rootScope, $state, $stateParams, $dialog, User, $translate, $uibModal) {
$scope.codeRequired = false;
// Get the app configuration
@@ -26,7 +26,15 @@ angular.module('docs').controller('Login', function(Restangular, $scope, $rootSc
User.userInfo(true).then(function(data) {
$rootScope.userInfo = data;
});
$state.go('document.default');
if($stateParams.redirectState !== undefined && $stateParams.redirectParams !== undefined) {
$state.go($stateParams.redirectState, JSON.parse($stateParams.redirectParams))
.catch(function() {
$state.go('document.default');
});
} else {
$state.go('document.default');
}
}, function(data) {
if (data.data.type === 'ValidationCodeRequired') {
// A TOTP validation code is required to login

View File

@@ -3,13 +3,18 @@
/**
* Navigation controller.
*/
angular.module('docs').controller('Navigation', function($scope, $state, $rootScope, User) {
angular.module('docs').controller('Navigation', function($scope, $state, $stateParams, $rootScope, User) {
User.userInfo().then(function(data) {
$rootScope.userInfo = data;
if (data.anonymous) {
$state.go('login', {}, {
location: 'replace'
});
if($state.current.name !== 'login') {
$state.go('login', {
redirectState: $state.current.name,
redirectParams: JSON.stringify($stateParams),
}, {
location: 'replace'
});
}
}
});

View File

@@ -173,7 +173,10 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
$scope.startSearch = function () {
var search = '';
if (!_.isEmpty($scope.advsearch.search_simple)) {
search += $scope.advsearch.search_simple + ' ';
var simplesearch = _.map($scope.advsearch.search_simple.split(/\s+/), function (simple) {
return 'simple:' + simple
});
search += simplesearch.join(' ') + ' ';
}
if (!_.isEmpty($scope.advsearch.search_fulltext)) {
var fulltext = _.map($scope.advsearch.search_fulltext.split(/\s+/), function (full) {

View File

@@ -13,7 +13,6 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
$scope.loadFiles = function () {
Restangular.one('file/list').get().then(function (data) {
$scope.files = data.files;
// TODO Keep currently uploading files
});
};
$scope.loadFiles();
@@ -121,7 +120,7 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
}
Restangular.withConfig(function (RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('https://api.sismicsdocs.com');
RestangularConfigurer.setBaseUrl('https://api.teedy.io');
}).one('api').post('feedback', {
content: content
}).then(function () {

View File

@@ -3,7 +3,7 @@
/**
* Document view controller.
*/
angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $uibModal, Restangular, $translate) {
angular.module('docs').controller('DocumentView', function ($scope, $rootScope, $state, $stateParams, $location, $dialog, $uibModal, Restangular, $translate) {
// Load document data from server
Restangular.one('document', $stateParams.id).get().then(function (data) {
$scope.document = data;
@@ -111,10 +111,13 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta
var title = $translate.instant('document.view.shared_document_title');
var msg = $translate.instant('document.view.shared_document_message', { link: link });
var btns = [
{result: 'unshare', label: $translate.instant('unshare'), cssClass: 'btn-danger'},
{result: 'close', label: $translate.instant('close')}
];
if ($rootScope.userInfo.username !== 'guest') {
btns.unshift({result: 'unshare', label: $translate.instant('unshare'), cssClass: 'btn-danger'});
}
$dialog.messageBox(title, msg, btns, function (result) {
if (result === 'unshare') {
// Unshare this document and update the local shares

View File

@@ -5,6 +5,7 @@
*/
angular.module('docs').controller('DocumentViewContent', function ($scope, $rootScope, $stateParams, Restangular, $dialog, $state, Upload, $translate, $uibModal) {
$scope.displayMode = _.isUndefined(localStorage.fileDisplayMode) ? 'grid' : localStorage.fileDisplayMode;
$scope.openedFile = undefined;
/**
* Watch for display mode change.
@@ -45,7 +46,6 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
$scope.loadFiles = function () {
Restangular.one('file/list').get({ id: $stateParams.id }).then(function (data) {
$scope.files = data.files;
// TODO Keep currently uploading files
});
};
$scope.loadFiles();
@@ -55,7 +55,8 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
*/
$scope.openFile = function (file, $event) {
if ($($event.target).parents('.currently-dragging').length === 0) {
$state.go('document.view.content.file', {id: $stateParams.id, fileId: file.id})
$scope.openedFile = file;
$state.go('document.view.content.file', { id: $stateParams.id, fileId: file.id });
}
};

View File

@@ -0,0 +1,33 @@
'use strict';
/**
* Settings LDAP page controller.
*/
angular.module('docs').controller('SettingsLdap', function($scope, Restangular, $translate, $timeout) {
$scope.ldap = {
enabled: false
};
// Get the LDAP configuration
Restangular.one('app/config_ldap').get().then(function (data) {
$scope.ldap = data;
if ($scope.ldap.default_storage) {
$scope.ldap.default_storage /= 1000000;
}
});
// Edit SMTP config
$scope.saveResult = undefined;
$scope.save = function () {
var ldap = angular.copy($scope.ldap);
if (ldap.default_storage) {
ldap.default_storage *= 1000000;
}
Restangular.one('app').post('config_ldap', ldap).then(function () {
$scope.saveResult = $translate.instant('settings.ldap.saved');
$timeout(function() {
$scope.saveResult = undefined;
}, 5000);
});
};
});

View File

@@ -0,0 +1,12 @@
'use strict';
/**
* Add space between element directive.
*/
angular.module('docs').directive('addSpaceBetween', function () {
return function (scope, element) {
if(!scope.$last) {
element.after('&#32;');
}
}
});

View File

@@ -0,0 +1,25 @@
'use strict';
/**
* Invert text color for more legibility directive.
*/
angular.module('docs').directive('invertTextColor', function () {
return {
restrict: 'A',
link: function(scope, element, attrs) {
attrs.$observe('invertTextColor', function(hex) {
if (!hex || hex.length !== 7) {
return;
}
hex = hex.slice(1);
var r = parseInt(hex.slice(0, 2), 16),
g = parseInt(hex.slice(2, 4), 16),
b = parseInt(hex.slice(4, 6), 16);
element.css('color', (r * 0.299 + g * 0.587 + b * 0.114) > 186
? '#000000'
: '#FFFFFF');
});
}
}
});

View File

@@ -9,6 +9,7 @@ angular.module('docs').directive('selectRelation', function() {
templateUrl: 'partial/docs/directive.selectrelation.html',
replace: true,
scope: {
id: '=',
relations: '=',
ref: '@',
ngDisabled: '='
@@ -18,21 +19,12 @@ angular.module('docs').directive('selectRelation', function() {
* Add a relation.
*/
$scope.addRelation = function($item) {
// Does the new relation is already in the model
var duplicate = _.find($scope.relations, function(relation) {
if ($item.id === relation.id) {
return relation;
}
});
// Add the new relation
if (!duplicate) {
$scope.relations.push({
id: $item.id,
title: $item.title,
source: true
});
}
$scope.relations.push({
id: $item.id,
title: $item.title,
source: true
});
$scope.input = '';
};
@@ -42,11 +34,11 @@ angular.module('docs').directive('selectRelation', function() {
$scope.deleteRelation = function(deleteRelation) {
$scope.relations = _.reject($scope.relations, function(relation) {
return relation.id === deleteRelation.id;
})
});
};
/**
* Returns a promise for typeahead title.
* Returns a promise for typeahead document.
*/
$scope.getDocumentTypeahead = function($viewValue) {
var deferred = $q.defer();
@@ -57,8 +49,16 @@ angular.module('docs').directive('selectRelation', function() {
asc: true,
search: $viewValue
}).then(function(data) {
deferred.resolve(data.documents);
});
deferred.resolve(_.reject(data.documents, function(document) {
var duplicate = _.find($scope.relations, function(relation) {
if (document.id === relation.id) {
return relation;
}
});
return document.id === $scope.id || duplicate;
}));
});
return deferred.promise;
};
},

View File

@@ -61,12 +61,15 @@ angular.module('share',
prefix: 'locale/',
suffix: '.json?@build.date@'
})
.registerAvailableLanguageKeys(['en', 'es', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
'ru_*': 'ru',
.registerAvailableLanguageKeys(['en', 'es', 'fr', 'de', 'el', 'ru', 'it', 'pl', 'zh_CN', 'zh_TW'], {
'en_*': 'en',
'es_*': 'es',
'fr_*': 'fr',
'de_*': 'de',
'el_*': 'el',
'ru_*': 'ru',
'it_*': 'it',
'pl_*': 'pl',
'*': 'en'
})
.fallbackLanguage('en');

View File

@@ -94,6 +94,7 @@
<script src="app/docs/controller/settings/SettingsGroupEdit.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/SettingsVocabulary.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/SettingsMetadata.js" type="text/javascript"></script>
<script src="app/docs/controller/settings/SettingsLdap.js" type="text/javascript"></script>
<script src="app/docs/controller/usergroup/UserGroup.js" type="text/javascript"></script>
<script src="app/docs/controller/usergroup/UserProfile.js" type="text/javascript"></script>
<script src="app/docs/controller/usergroup/GroupProfile.js" type="text/javascript"></script>
@@ -106,6 +107,8 @@
<script src="app/docs/directive/Acl.js" type="text/javascript"></script>
<script src="app/docs/directive/AclEdit.js" type="text/javascript"></script>
<script src="app/docs/directive/Pell.js" type="text/javascript"></script>
<script src="app/docs/directive/AddSpaceBetween.js" type="text/javascript"></script>
<script src="app/docs/directive/InvertTextColor.js" type="text/javascript"></script>
<!-- endref -->
</head>
<body translate-cloak ng-cloak>
@@ -181,8 +184,11 @@
<span ng-switch-when="en">English</span>
<span ng-switch-when="fr">Français</span>
<span ng-switch-when="de">Deutsch</span>
<span ng-switch-when="it">Italiano</span>
<span ng-switch-when="es">Española</span>
<span ng-switch-when="ru">русский</span>
<span ng-switch-when="el">Ελληνικά</span>
<span ng-switch-when="ru">Pусский</span>
<span ng-switch-when="pl">Polski</span>
<span ng-switch-when="zh_CN">简体中文</span>
<span ng-switch-when="zh_TW">繁體中文</span>
</span>
@@ -192,9 +198,12 @@
<li><a href ng-click="changeLanguage('en')" ng-class="{ 'bg-info': currentLang == 'en' }">English</a></li>
<li><a href ng-click="changeLanguage('fr')" ng-class="{ 'bg-info': currentLang == 'fr' }">Français</a></li>
<li><a href ng-click="changeLanguage('de')" ng-class="{ 'bg-info': currentLang == 'de' }">Deutsch</a></li>
<li><a href ng-click="changeLanguage('it')" ng-class="{ 'bg-info': currentLang == 'it' }">Italiano</a></li>
<li><a href ng-click="changeLanguage('es')" ng-class="{ 'bg-info': currentLang == 'es' }">Española</a></li>
<li><a href ng-click="changeLanguage('ru')" ng-class="{ 'bg-info': currentLang == 'ru' }">русский</a></li>
<li><a href ng-click="changeLanguage('zh_CN')" ng-class="{ 'bg-info': currentLang == 'zh_CN' }">简体中文</a></li>
<li><a href ng-click="changeLanguage('el')" ng-class="{ 'bg-info': currentLang == 'el' }">Ελληνικά</a></li>
<li><a href ng-click="changeLanguage('ru')" ng-class="{ 'bg-info': currentLang == 'ru' }">Pусский</a></li>
<li><a href ng-click="changeLanguage('pl')" ng-class="{ 'bg-info': currentLang == 'pl' }">Polski</a></li>
<li><a href ng-click="changeLanguage('zh_CN')" ng-class="{ 'bg-info': currentLang == 'zh_CN' }">简体中文</a></li>
<li><a href ng-click="changeLanguage('zh_TW')" ng-class="{ 'bg-info': currentLang == 'zh_TW' }">繁體中文</a></li>
</ul>
</li>

View File

@@ -34,6 +34,29 @@ angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(tim
'use strict';
angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(timeAgoSettings) {
timeAgoSettings.strings['el'] = {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: 'πριν',
suffixFromNow: 'από τώρα',
seconds: 'λιγότερο από ένα λεπτό',
minute: 'περίπου ένα λεπτό',
minutes: '%d λεπτά',
hour: 'περίπου μια ώρα',
hours: 'περίπου %d ώρες',
day: 'μια μέρα',
days: '%d μέρες',
month: 'περίπου ένα μήνα',
months: '%d μήνες',
year: 'περίπου ένα χρόνο',
years: '%d χρόνια',
numbers: []
};
}]);
'use strict';
angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(timeAgoSettings) {
/**
@@ -288,7 +311,7 @@ angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(tim
'use strict';
angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(timeAgoSettings) {
timeAgoSettings.strings['it_IT'] = {
timeAgoSettings.strings['it'] = {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: 'fa',
@@ -356,6 +379,29 @@ angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(tim
'use strict';
angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(timeAgoSettings) {
timeAgoSettings.strings['pl'] = {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: 'temu',
suffixFromNow: 'od teraz',
seconds: 'mniej niż minuta',
minute: 'około minuty',
minutes: '%d minut',
hour: 'około godziny',
hours: 'około %d godzin',
day: 'dzień',
days: '%d dni',
month: 'około miesiąca',
months: '%d miesięcy',
year: 'około roku',
years: '%d lat',
numbers: []
};
}]);
'use strict';
angular.module('yaru22.angular-timeago').config(["timeAgoSettings", function(timeAgoSettings) {
timeAgoSettings.strings['pt_BR'] = {
prefixAgo: null,

View File

@@ -104,7 +104,7 @@ $provide.value("$locale", {
"mediumDate": "dd.MM.y",
"mediumTime": "HH:mm:ss",
"short": "dd.MM.yy HH:mm",
"shortDate": "dd.MM.yy",
"shortDate": "dd.MM.yyyy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {

View File

@@ -0,0 +1,125 @@
'use strict';
angular.module("ngLocale", [], ["$provide", function($provide) {
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
$provide.value("$locale", {
"DATETIME_FORMATS": {
"AMPMS": [
"\u03c0.\u03bc.",
"\u03bc.\u03bc."
],
"DAY": [
"\u039a\u03c5\u03c1\u03b9\u03b1\u03ba\u03ae",
"\u0394\u03b5\u03c5\u03c4\u03ad\u03c1\u03b1",
"\u03a4\u03c1\u03af\u03c4\u03b7",
"\u03a4\u03b5\u03c4\u03ac\u03c1\u03c4\u03b7",
"\u03a0\u03ad\u03bc\u03c0\u03c4\u03b7",
"\u03a0\u03b1\u03c1\u03b1\u03c3\u03ba\u03b5\u03c5\u03ae",
"\u03a3\u03ac\u03b2\u03b2\u03b1\u03c4\u03bf"
],
"ERANAMES": [
"\u03c0\u03c1\u03bf \u03a7\u03c1\u03b9\u03c3\u03c4\u03bf\u03cd",
"\u03bc\u03b5\u03c4\u03ac \u03a7\u03c1\u03b9\u03c3\u03c4\u03cc\u03bd"
],
"ERAS": [
"\u03c0.\u03a7.",
"\u03bc.\u03a7."
],
"FIRSTDAYOFWEEK": 0,
"MONTH": [
"\u0399\u03b1\u03bd\u03bf\u03c5\u03b1\u03c1\u03af\u03bf\u03c5",
"\u03a6\u03b5\u03b2\u03c1\u03bf\u03c5\u03b1\u03c1\u03af\u03bf\u03c5",
"\u039c\u03b1\u03c1\u03c4\u03af\u03bf\u03c5",
"\u0391\u03c0\u03c1\u03b9\u03bb\u03af\u03bf\u03c5",
"\u039c\u03b1\u0390\u03bf\u03c5",
"\u0399\u03bf\u03c5\u03bd\u03af\u03bf\u03c5",
"\u0399\u03bf\u03c5\u03bb\u03af\u03bf\u03c5",
"\u0391\u03c5\u03b3\u03bf\u03cd\u03c3\u03c4\u03bf\u03c5",
"\u03a3\u03b5\u03c0\u03c4\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5",
"\u039f\u03ba\u03c4\u03c9\u03b2\u03c1\u03af\u03bf\u03c5",
"\u039d\u03bf\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5",
"\u0394\u03b5\u03ba\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5"
],
"SHORTDAY": [
"\u039a\u03c5\u03c1",
"\u0394\u03b5\u03c5",
"\u03a4\u03c1\u03af",
"\u03a4\u03b5\u03c4",
"\u03a0\u03ad\u03bc",
"\u03a0\u03b1\u03c1",
"\u03a3\u03ac\u03b2"
],
"SHORTMONTH": [
"\u0399\u03b1\u03bd",
"\u03a6\u03b5\u03b2",
"\u039c\u03b1\u03c1",
"\u0391\u03c0\u03c1",
"\u039c\u03b1\u0390",
"\u0399\u03bf\u03c5\u03bd",
"\u0399\u03bf\u03c5\u03bb",
"\u0391\u03c5\u03b3",
"\u03a3\u03b5\u03c0",
"\u039f\u03ba\u03c4",
"\u039d\u03bf\u03b5",
"\u0394\u03b5\u03ba"
],
"STANDALONEMONTH": [
"\u0399\u03b1\u03bd\u03bf\u03c5\u03ac\u03c1\u03b9\u03bf\u03c2",
"\u03a6\u03b5\u03b2\u03c1\u03bf\u03c5\u03ac\u03c1\u03b9\u03bf\u03c2",
"\u039c\u03ac\u03c1\u03c4\u03b9\u03bf\u03c2",
"\u0391\u03c0\u03c1\u03af\u03bb\u03b9\u03bf\u03c2",
"\u039c\u03ac\u03b9\u03bf\u03c2",
"\u0399\u03bf\u03cd\u03bd\u03b9\u03bf\u03c2",
"\u0399\u03bf\u03cd\u03bb\u03b9\u03bf\u03c2",
"\u0391\u03cd\u03b3\u03bf\u03c5\u03c3\u03c4\u03bf\u03c2",
"\u03a3\u03b5\u03c0\u03c4\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2",
"\u039f\u03ba\u03c4\u03ce\u03b2\u03c1\u03b9\u03bf\u03c2",
"\u039d\u03bf\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2",
"\u0394\u03b5\u03ba\u03ad\u03bc\u03b2\u03c1\u03b9\u03bf\u03c2"
],
"WEEKENDRANGE": [
5,
6
],
"fullDate": "EEEE, d MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y h:mm:ss a",
"mediumDate": "d MMM y",
"mediumTime": "h:mm:ss a",
"short": "d/M/yy h:mm a",
"shortDate": "d/M/yy",
"shortTime": "h:mm a"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "\u20ac",
"DECIMAL_SEP": ",",
"GROUP_SEP": ".",
"PATTERNS": [
{
"gSize": 3,
"lgSize": 3,
"maxFrac": 3,
"minFrac": 0,
"minInt": 1,
"negPre": "-",
"negSuf": "",
"posPre": "",
"posSuf": ""
},
{
"gSize": 3,
"lgSize": 3,
"maxFrac": 2,
"minFrac": 2,
"minInt": 1,
"negPre": "-",
"negSuf": "\u00a0\u00a4",
"posPre": "",
"posSuf": "\u00a0\u00a4"
}
]
},
"id": "el",
"localeID": "el",
"pluralCat": function(n, opt_precision) { if (n == 1) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
});
}]);

View File

@@ -103,8 +103,8 @@ $provide.value("$locale", {
"medium": "MMM d, y h:mm:ss a",
"mediumDate": "MMM d, y",
"mediumTime": "h:mm:ss a",
"short": "M/d/yy h:mm a",
"shortDate": "M/d/yy",
"short": "yy/M/d h:mm a",
"shortDate": "yyyy/MM/dd",
"shortTime": "h:mm a"
},
"NUMBER_FORMATS": {

View File

@@ -86,7 +86,7 @@ $provide.value("$locale", {
"mediumDate": "d MMM y",
"mediumTime": "H:mm:ss",
"short": "d/M/yy H:mm",
"shortDate": "d/M/yy",
"shortDate": "dd/MM/yyyy",
"shortTime": "H:mm"
},
"NUMBER_FORMATS": {

View File

@@ -86,7 +86,7 @@ $provide.value("$locale", {
"mediumDate": "d MMM y",
"mediumTime": "HH:mm:ss",
"short": "dd/MM/y HH:mm",
"shortDate": "dd/MM/y",
"shortDate": "dd/MM/yyyy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {

View File

@@ -0,0 +1,143 @@
'use strict';
angular.module("ngLocale", [], ["$provide", function($provide) {
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
function getDecimals(n) {
n = n + '';
var i = n.indexOf('.');
return (i == -1) ? 0 : n.length - i - 1;
}
function getVF(n, opt_precision) {
var v = opt_precision;
if (undefined === v) {
v = Math.min(getDecimals(n), 3);
}
var base = Math.pow(10, v);
var f = ((n * base) | 0) % base;
return {v: v, f: f};
}
$provide.value("$locale", {
"DATETIME_FORMATS": {
"AMPMS": [
"AM",
"PM"
],
"DAY": [
"domenica",
"luned\u00ec",
"marted\u00ec",
"mercoled\u00ec",
"gioved\u00ec",
"venerd\u00ec",
"sabato"
],
"ERANAMES": [
"avanti Cristo",
"dopo Cristo"
],
"ERAS": [
"a.C.",
"d.C."
],
"FIRSTDAYOFWEEK": 0,
"MONTH": [
"gennaio",
"febbraio",
"marzo",
"aprile",
"maggio",
"giugno",
"luglio",
"agosto",
"settembre",
"ottobre",
"novembre",
"dicembre"
],
"SHORTDAY": [
"dom",
"lun",
"mar",
"mer",
"gio",
"ven",
"sab"
],
"SHORTMONTH": [
"gen",
"feb",
"mar",
"apr",
"mag",
"giu",
"lug",
"ago",
"set",
"ott",
"nov",
"dic"
],
"STANDALONEMONTH": [
"gennaio",
"febbraio",
"marzo",
"aprile",
"maggio",
"giugno",
"luglio",
"agosto",
"settembre",
"ottobre",
"novembre",
"dicembre"
],
"WEEKENDRANGE": [
5,
6
],
"fullDate": "EEEE d MMMM y",
"longDate": "d MMMM y",
"medium": "dd MMM y HH:mm:ss",
"mediumDate": "dd MMM y",
"mediumTime": "HH:mm:ss",
"short": "dd/MM/yy HH:mm",
"shortDate": "dd/MM/yy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "\u20ac",
"DECIMAL_SEP": ",",
"GROUP_SEP": ".",
"PATTERNS": [
{
"gSize": 3,
"lgSize": 3,
"maxFrac": 3,
"minFrac": 0,
"minInt": 1,
"negPre": "-",
"negSuf": "",
"posPre": "",
"posSuf": ""
},
{
"gSize": 3,
"lgSize": 3,
"maxFrac": 2,
"minFrac": 2,
"minInt": 1,
"negPre": "-",
"negSuf": "\u00a0\u00a4",
"posPre": "",
"posSuf": "\u00a0\u00a4"
}
]
},
"id": "it",
"localeID": "it",
"pluralCat": function(n, opt_precision) { var i = n | 0; var vf = getVF(n, opt_precision); if (i == 1 && vf.v == 0) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
});
}]);

View File

@@ -0,0 +1,125 @@
'use strict';
angular.module("ngLocale", [], ["$provide", function($provide) {
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "jeden", TWO: "dwa", FEW: "trochę", MANY: "wiele", OTHER: "pozostałe"};
$provide.value("$locale", {
"DATETIME_FORMATS": {
"AMPMS": [
"AM",
"PM"
],
"DAY": [
"Niedziela",
"Poniedziałek",
"Wtorek",
"Środa",
"Czwartek",
"Piątek",
"Sobota"
],
"ERANAMES": [
"przed Chrystusem",
"roku Pańskiego"
],
"ERAS": [
"BC",
"AD"
],
"FIRSTDAYOFWEEK": 6,
"MONTH": [
"Styczeń",
"Luty",
"Marzec",
"Kwiecień",
"Maj",
"Czerwic",
"Lipiec",
"Sierpień",
"Wrzesień",
"Październik",
"Listopad",
"Grudzień"
],
"SHORTDAY": [
"N",
"Pn",
"Wt",
"Śr",
"Cz",
"Pt",
"So"
],
"SHORTMONTH": [
"Sty",
"Lut",
"Mar",
"Kwi",
"Maj",
"Cze",
"Lip",
"Sie",
"Wrz",
"Paź",
"Lis",
"Gru"
],
"STANDALONEMONTH": [
"Styczeń",
"Luty",
"Marzec",
"Kwiecień",
"Maj",
"Czerwiec",
"Lipiec",
"Sierpień",
"Wrzesień",
"Październik",
"Listopad",
"Grudzień"
],
"WEEKENDRANGE": [
5,
6
],
"fullDate": "EEEE d MMMM y",
"longDate": "d MMMM y",
"medium": "d MMM y HH:mm:ss",
"mediumDate": "d MMM y",
"mediumTime": "HH:mm:ss",
"short": "dd-MM-yyyy HH:mm",
"shortDate": "dd-MM-yyyy",
"shortTime": "HH:mm"
},
"NUMBER_FORMATS": {
"CURRENCY_SYM": "zł",
"DECIMAL_SEP": ",",
"GROUP_SEP": ".",
"PATTERNS": [
{
"gSize": 3,
"lgSize": 3,
"maxFrac": 3,
"minFrac": 0,
"minInt": 1,
"negPre": "-",
"negSuf": "",
"posPre": "",
"posSuf": ""
},
{
"gSize": 3,
"lgSize": 3,
"maxFrac": 2,
"minFrac": 2,
"minInt": 1,
"negPre": "-\u00a4",
"negSuf": "",
"posPre": "\u00a4",
"posSuf": ""
}
]
},
"id": "pl",
"localeID": "pl",
"pluralCat": function(n, opt_precision) { if (n == 1) { return PLURAL_CATEGORY.ONE; } return PLURAL_CATEGORY.OTHER;}
});
}]);

View File

@@ -86,7 +86,7 @@ $provide.value("$locale", {
"mediumDate": "y\u5e74M\u6708d\u65e5",
"mediumTime": "ah:mm:ss",
"short": "y/M/d ah:mm",
"shortDate": "y/M/d",
"shortDate": "yyyy/MM/dd",
"shortTime": "ah:mm"
},
"NUMBER_FORMATS": {

View File

@@ -86,7 +86,7 @@ $provide.value("$locale", {
"mediumDate": "y\u5e74M\u6708d\u65e5",
"mediumTime": "ah:mm:ss",
"short": "y/M/d ah:mm",
"shortDate": "y/M/d",
"shortDate": "yyyy/MM/dd",
"shortTime": "ah:mm"
},
"NUMBER_FORMATS": {

View File

@@ -96,7 +96,7 @@
"shared_document_message": "Sie können dieses Dokument mit diesem Link freigeben. Beachten Sie, dass jeder, der diesen Link hat, das Dokument sehen kann.<br/><input class=\"form-control share-link\" type=\"text\" readonly=\"readonly\" value=\"{{ link }}\" onclick=\"this.select(); document.execCommand('copy');\" />",
"not_found": "Dokument nicht gefunden",
"forbidden": "Zugriff verweigert",
"download_files": "Datei herunterladen",
"download_files": "Dateien herunterladen",
"export_pdf": "in PDF exportieren",
"by_creator": "von",
"comments": "Kommentar",
@@ -107,6 +107,8 @@
"workflow_comment": "Fügen Sie einen Workflow Kommentar hinzu",
"workflow_validated_title": "Workflow-Schritt validiert",
"workflow_validated_message": "Der Workflow-Schritt wurde erfolgreich validiert.",
"display_mode_list": "Dateien in Liste anzeigen",
"display_mode_grid": "Dateien im Raster anzeigen",
"content": {
"content": "Inhalt",
"delete_file_title": "Datei löschen",
@@ -276,6 +278,7 @@
"menu_vocabularies": "Vokabulareinträge",
"menu_configuration": "Einstellungen",
"menu_inbox": "Posteingang durchsuchen",
"menu_metadata": "Benutzerdefinierte Metadaten",
"menu_monitoring": "Überwachung",
"user": {
"title": "Benutzerverwaltung",
@@ -286,6 +289,8 @@
"edit": {
"delete_user_title": "Benutzer löschen",
"delete_user_message": "Möchten Sie diesen Benutzer wirklich löschen? Alle zugehörigen Dokumente, Dateien und Tags werden gelöscht",
"user_used_title": "Benutzer in Verwendung",
"user_used_message": "Dieser Benutzer wird im Workflow \"{{ name }}\" benutzt",
"edit_user_failed_title": "Dieser Benutzer existiert bereits",
"edit_user_failed_message": "Dieser Benutzername wurde bereits von einem anderen Benutzer gewählt",
"edit_user_title": "Bearbeiten \"{{ username }}\"",
@@ -347,7 +352,10 @@
"message": "Ihr Konto wird nicht mehr durch die Zwei-Faktor-Authentifizierung geschützt.",
"confirm_password": "Bestätigen Sie ihr Passwort",
"submit": "Deaktivieren der Zwei-Faktor-Authentifizierung"
}
},
"test_totp": "Bitte geben Sie den auf Ihrem Telefon angezeigten Validierungscode ein:",
"test_code_success": "Validierungscode OK",
"test_code_fail": "Dieser Code ist nicht gültig. Überprüfen Sie, ob Ihr Telefon ordnungsgemäß konfiguriert ist, oder deaktivieren Sie die Zwei-Faktor-Authentifizierung"
},
"group": {
"title": "Gruppenverwaltung",
@@ -358,6 +366,8 @@
"delete_group_message": "Wollen Sie diese Gruppe wirklich löschen?",
"edit_group_failed_title": "Gruppe existiert bereits",
"edit_group_failed_message": "Dieser Gruppenname wird bereits von einer anderen Gruppe übernommen",
"group_used_title": "Gruppe in Verwendung",
"group_used_message": "Diese Gruppe wird im Workflow \"{{ name }}\" verwendet",
"edit_group_title": "Bearbeiten \"{{ name }}\"",
"add_group_title": "Neue Gruppe hinzufügen",
"name": "Name",
@@ -403,6 +413,12 @@
"webhook_create_date": "Erstelldatum",
"webhook_add": "Webhook hinzufügen"
},
"metadata": {
"title": "Konfiguration benutzerdefinierter Metadaten",
"message": "Hier können Sie Ihren Dokumenten benutzerdefinierte Metadaten wie eine interne Kennung oder ein Ablaufdatum hinzufügen. Bitte beachten Sie, dass der Metadatentyp nach der Erstellung nicht mehr geändert werden kann.",
"name": "Metadatensatz Name",
"type": "Metadatensatz Typ"
},
"inbox": {
"title": "Posteingang durchsuchen",
"message": "Wenn Sie diese Funktion aktivieren, durchsucht das System den angegebenen Posteingang jede Minute nach <strong>ungelesenen</strong> E-Mails und importiert diese automatisch.<br/>Nach dem Import einer E-Mail wird diese als gelesen markiert.<br/>Folgen Sie den Links zu Konfigurationseinstellungen für <a href=\"https://support.google.com/mail/answer/7126229?hl=en\" target=\"_blank\">Gmail</a>, <a href=\"https://support.office.com/en-us/article/pop-imap-and-smtp-settings-for-outlook-com-d088b986-291d-42b8-9564-9c414e2aa040\" target=\"_blank\">Outlook.com</a>, <a href=\"https://help.yahoo.com/kb/SLN4075.html\" target=\"_blank\">Yahoo</a>.",
@@ -411,6 +427,7 @@
"port": "IMAP Port (143 oder 993)",
"username": "IMAP Benutzername",
"password": "IMAP Passwort",
"folder": "IMAP Ordner",
"tag": "Folgenden Tag zu importierten Dokumenten hinzufügen",
"test": "Konfiguration testen",
"last_sync": "Letzte Synchronisation: {{ data.date | date: 'medium' }}, {{ data.count }} E-Mail(s){{ data.count > 1 ? 's' : '' }} importiert",
@@ -540,7 +557,7 @@
"VALIDATED": "Validiert"
},
"validation": {
"required": "Erfordert",
"required": "Erforderlich",
"too_short": "Zu kurz",
"too_long": "Zu lang",
"email": "Muss eine gültige E-Mailadresse sein",
@@ -559,6 +576,30 @@
"first": "Erste",
"last": "Letzte"
},
"onboarding": {
"step1": {
"title": "Das erste Mal?",
"description": "Wenn Sie Teedy zum ersten Mal nutzen, klicken Sie auf die Schaltfläche Weiter. Andernfalls können Sie mich schließen."
},
"step2": {
"title": "Dokumente",
"description": "Teedy ist in Dokumenten organisiert und jedes Dokument enthält mehrere Dateien."
},
"step3": {
"title": "Dateien",
"description": "Sie können Dateien hinzufügen, nachdem Sie ein Dokument erstellt haben oder vorher, indem Sie diesen Schnell-Hochlade-Bereich verwenden."
},
"step4": {
"title": "Suche",
"description": "Dies ist die Hauptmethode, um Ihre Dokumente wiederzufinden. Es gibt auch eine erweiterte Suche mit dem Suchbutton."
},
"step5": {
"title": "Tags",
"description": "Dokumente können in Tags organisiert werden (ähnlich wie Oberordner). Erstellen Sie sie hier."
}
},
"yes": "Ja",
"no": "Nein",
"ok": "OK",
"cancel": "Abbrechen",
"share": "Teilen",
@@ -572,8 +613,9 @@
"edit": "Bearbeiten",
"delete": "Löschen",
"rename": "Umbenennen",
"download": "Herunterladen",
"loading": "Lädt...",
"send": "Absenden",
"enabled": "Aktiviert",
"disabled": "Deaktiviert"
}
}

View File

@@ -0,0 +1,638 @@
{
"login": {
"username": "Όνομα Χρήστη",
"password": "Κωδικός",
"validation_code_required": "Απαιτείται κωδικός επαλήθευσης",
"validation_code_title": "Έχετε ενεγροποιήσει την επαλήθευση δύο σταδίων στο λογαριασμό σας. Παρακαλούμε εισάγετε ένα κωδικό επαλήθευσης από την εφαρμογή κινητού που έχετε ρυθμίσει.",
"validation_code": "Κωδικός επαλήθευσης",
"remember_me": "Να με θυμάσαι",
"submit": "Σύνδεση",
"login_as_guest": "Σύνδεση ως επισκέπτης",
"login_failed_title": "Αποτυχία σύνδεσης",
"login_failed_message": "Λανθασμένο όνομα χρήστη ή κωδικός",
"password_lost_btn": "Δεν θυμάσαι τον κωδικό;",
"password_lost_sent_title": "Έχει σταλεί το email επαναφοράς κωδικού",
"password_lost_sent_message": "Ένα email έχει σταλεί στο <strong>{{ username }}</strong> για την επαναφορά του κωδικού σου",
"password_lost_error_title": "Σφάλμα επαναφοράς κωδικού",
"password_lost_error_message": "Δεν κατέστη δυνατή η αποστολή email επαναφοράς κωδικού, παρακαλούμε επικοινωνήστε με το διαχειριστή για χειροκίνητη επαναφορά"
},
"passwordlost": {
"title": "Ξέχασα τον κωδικό",
"message": "Παρακαλούμε συμπλήρωσε το όνομα χρήστη σου για να λάβεις ένα σύνδεσμο επαναφοράς κωδικού. Αν δεν θυμάσαι το όνομα χρήστη, παρακαλούμε επικοινώνησε με το διαχειριστή",
"submit": "Επαναφορά του κωδικού μου"
},
"passwordreset": {
"message": "Παρακαλούμε συμπλήρωσε ένα νέο κωδικό",
"submit": "Αλλαγή του κωδικού μου",
"error_title": "Σφάλμα αλλαγής του κωδικού σου",
"error_message": "Το αίτημα ανάκτησης του κωδικού σου έχει λείξει, παρακαλούμε ζήτησε ένα νέο στη σελίδα σύνδεσης"
},
"index": {
"toggle_navigation": "Μετακίνηση πλοήγησης",
"nav_documents": "έγγραφα",
"nav_tags": "Ετικέτες",
"nav_users_groups": "Χρήστες & Ομάδες",
"error_info": "{{ count }} Νέο σφάλμα",
"logged_as": "Σύνδεση ως {{ username }}",
"nav_settings": "Ρυθμίσεις",
"logout": "Αποσύνδεση",
"global_quota_warning": "<strong>Προειδοποίηση!</strong> Η παγκόμσια ποσόστωση έχει φτάσει στις {{ current | number: 0 }}MB ({{ percent | number: 1 }}%) με χρήση στις {{ total | number: 0 }}MB"
},
"document": {
"navigation_up": "Ανέβα ένα επίπεδο",
"toggle_navigation": "Μετακίνηση φακέλου πλοήγησης",
"display_mode_list": "Εμφάνιση εγγράφων σε λίστα",
"display_mode_grid": "Εμφάνιση εγγράφων σε πλέγμα",
"search_simple": "Απλή αναζήτηση",
"search_fulltext": "Αναζήτηση πλήρους κειμένου",
"search_creator": "Δημιουργός",
"search_language": "Γλώσσα",
"search_before_date": "Δημιουργήθηκε πριν από αυτή την ημερομηνία",
"search_after_date": "Δημιουργήθηκε μετά από αυτή την ημερομηνία",
"search_before_update_date": "Ενημερώθηκε πριν από αυτή την ημερομηνία",
"search_after_update_date": "Ενημερώθηκε μετά από αυτή την ημερομηνία",
"search_tags": "Ετικέτες",
"search_shared": "Μόνο κοινοποιημένα εγγραφα",
"search_workflow": "Ροή εγρασίας ανατεθειμένη σε εμένα",
"search_clear": "Καθάρισμα",
"any_language": "Οποιαδήποτε γλώσσα",
"add_document": "Προσθήκη εγγράφου",
"import_eml": "Εισαγωγή απλο ένα email (μορφή EML)",
"tags": "Ετικέτες",
"no_tags": "Δεν υπάρχουν ετικέτες",
"no_documents": "Δεν υπάρχει έγγραφο στη βάση δεδομένων",
"search": "Αναζήτηση",
"search_empty": "Δεν υπάρχουν αποτελέσματα για <strong>\"{{ search }}\"</strong>",
"shared": "Κοινοποιήθηκε",
"current_step_name": "Τρέχον βήμα",
"title": "Τίτλος",
"description": "Περιγραφή",
"contributors": "Συντελεστές",
"language": "Γλώσσα",
"creation_date": "Ημερομηνία δημιουργίας",
"subject": "Θέμα",
"identifier": "Αναγωριστικό",
"publisher": "Εκδότης",
"format": "Μορφή",
"source": "Πηγή",
"type": "Είδος",
"coverage": "Κάλυψη",
"rights": "Δικαιώματα",
"relations": "Σχέσεις",
"page_size": "Μέγεθος σελίδας",
"page_size_10": "10 ανά σελίδα",
"page_size_20": "20 ανά σελίδα",
"page_size_30": "30 ανά σελίδα",
"upgrade_quota": "Για να αυξήσεις το ποσοστό σου, ρώτησε τον διαχειριστή σου",
"quota": "{{ current | number: 0 }}MB ({{ percent | number: 1 }}%) χρήση σε {{ total | number: 0 }}MB",
"count": "{{ count }} Έγγραφα βρέθηκε",
"last_updated": "Τελευταία ενημέρωση {{ date | timeAgo: dateFormat }}",
"view": {
"delete_comment_title": "Διαγραφή σχολίου",
"delete_comment_message": "Θέλεις πραγματικά να διαγράψεις αυτό το σχόλιο;",
"delete_document_title": "Διαγραφή εγγράφου",
"delete_document_message": "Θες πραγματικά να διαγράψεις αυτό το έγγραφο;",
"shared_document_title": "Κοινοποιημένο έγγραφο",
"shared_document_message": "Μπορείτε να κοινοποιήσετε αυτό το έγγραφο δίνοντας αυτό το σύνδεσμο. Σημειώστε πως όλοι όσοι έχουν αυτό το σύνδεσμο μπορούν να δουν το έγγραφο.<br/><input class=\"form-control share-link\" type=\"text\" readonly=\"readonly\" value=\"{{ link }}\" onclick=\"this.select(); document.execCommand('copy');\" />",
"not_found": "Δεν βρέθηκε έγγραφο",
"forbidden": "Η πρόσβαση απαγορεύτηκε",
"download_files": "Κατέβασμα αρχείου",
"export_pdf": "Εξαγωγή σε PDF",
"by_creator": "από",
"comments": "Σχόλια",
"no_comments": "Δεν υπάρχουν σχόλια σε αυτό το έγγραφο ακόμα",
"add_comment": "Προσθήκη σχολίου",
"error_loading_comments": "Σφάλμα φόρτωσης σχολίων",
"workflow_current": "Τρέχον βήμα ροής εργασίας",
"workflow_comment": "Προσθήκη σχολίου ροής εργασίας",
"workflow_validated_title": "Το βήμα ροής εργασίας επαληθεύτηκε",
"workflow_validated_message": "Το βήμα ροής εργασίας έχει επαληθευτεί επιτυχώς.",
"content": {
"content": "Περιεχόμενο",
"delete_file_title": "Διαγραφή αρχείου",
"delete_file_message": "Θέλεις πραγματικά να διαγράψεις αυτο το αρχείο;",
"upload_pending": "Εκκρεμεί...",
"upload_progress": "Ανέβασμα...",
"upload_error": "Σφάλμα ανεβάσματος",
"upload_error_quota": "Το ποσοστό έχει φτάσει",
"drop_zone": "Σύρε & άφησε αρχεία εδώ για ανέβασμα",
"add_files": "Προσθήκη αρχείων",
"file_processing_indicator": "Γίνεται επεξεργασία αυτού του αρχείου. Η αναζήτηση δεν θα είναι διαθέσιμη πριν ολοκληρωθεί.",
"reprocess_file": "Επανάληψη επεξεργασίας αυτού του αρχείου",
"upload_new_version": "Ανλεβασε μια νέα έκδοση",
"open_versions": "Προβολή ιστορικού εκδόσεων",
"display_mode_list": "Εμφάνιση αρχείων σε λίστα",
"display_mode_grid": "Εμφάνιση αρχείων σε πλέγμα"
},
"workflow": {
"workflow": "Ροή εργασίας",
"message": "Επαλήθευσε ή επιβεβαίωσε τα έγγραφα σου με άτομα του οργανισμού σου με τη χρήση ροών εργασίας.",
"workflow_start_label": "Ποιά ροή εργασίας να ξεκινήσει;",
"add_more_workflow": "Προσθήκη περισσότερων ροών εργασίας",
"start_workflow_submit": "Έναρξη ροής εργασίας",
"full_name": "<strong>{{ name }}</strong> ξεκίνησε στις {{ create_date | date }}",
"cancel_workflow": "Ακύρωση τρέχουσας ροής εργασίας",
"cancel_workflow_title": "Ακύρωση της ροής εργασίας",
"cancel_workflow_message": "Θέλεις πραγματικά να ακυρώσης την τρέχουσα ροή εργασίας;",
"no_workflow": "Δεν μπορείς να ξεκινήσεις οποιαδήποτε ροή εργασίας σε αυτό το έγγραφο."
},
"permissions": {
"permissions": "ʼδειες",
"message": "ʼδειες μπορούν να εφαρμοστούν απευθείας σε αυτό το έγγραφο, ή μπορείτε να έρθετε από <a href=\"#/tag\">tags</a>.",
"title": "ʼδειες σε αυτό το έγγραφο",
"inherited_tags": "ʼδειες που έχουν παρθεί από ετικέτες",
"acl_source": "Από",
"acl_target": "Για",
"acl_permission": "ʼδεια"
},
"activity": {
"activity": "Δραστηριότητα",
"message": "Κάθε ενέργειες σε αυτό το έγγραφο καταγράφονται εδώ."
}
},
"edit": {
"document_edited_with_errors": "Το έγγραφο επεξεργάστηκε επιτυχώς αλλά μερικά αρχεία δεν μπόρεσαν να ανέβουν",
"document_added_with_errors": "Το έγγραφο προστέθηκε επιτυχώς αλλά μερικά αρχεία δεν μπόρεσαν να ανέβουν",
"quota_reached": "Το ποσοστό έχει φτάσει",
"primary_metadata": "Κυρίως μεταδεδομένα",
"title_placeholder": "Ένα όνομα δόθηκε στη πηγή",
"description_placeholder": "Ένας λογαριασμός πηγής",
"new_files": "Νέα αρχεία",
"orphan_files": "+ {{ count }} αρχείο",
"additional_metadata": "Επιπρόσθετα μεταδεδομένα",
"subject_placeholder": "Το θέμα της πηγής",
"identifier_placeholder": "Μια ξεκάθαρη αναφορά στη πηγή σε συγκεκριμένο πλαίσιο",
"publisher_placeholder": "Μια οντότητα υπεύθυνη για να κάνει την πηγή διαθέσιμη",
"format_placeholder": "Η μορφή αρχείου, φυσικό ενδιάμεσο, ή διαστάσεις της πηγής",
"source_placeholder": "Μια σχετική πηγή από την οποία προέρχεται η περιγραφόμενη πηγή",
"uploading_files": "Ανέβασμα αρχείων..."
},
"default": {
"upload_pending": "Εκκρεμεί...",
"upload_progress": "Ανεβάζει...",
"upload_error": "Σφάλμα ανεβάσματος",
"upload_error_quota": "Το ποσοστό έχει φτάσει",
"quick_upload": "Γρήγορο ανέβασμα",
"drop_zone": "Σύρε & άφησε αρχεία εδώ για ανέβασμα",
"add_files": "Προσθήκη αρχείων",
"add_new_document": "Προσθήκη νέεου εγγράφου",
"latest_activity": "Τελευταία δραστηριότητα",
"footer_sismics": "Δημιουργήθηκε με <span class=\"fas fa-heart\"></span> από <a href=\"https://www.sismics.com\" target=\"_blank\">Sismics</a>",
"api_documentation": "API Έγγραφα",
"feedback": "Δώσε μας ανατροφοδότηση",
"workflow_document_list": "Έγγρφα που ανατέθηκαν σε εσένα",
"select_all": "Επιλογή όλων",
"select_none": "Επιλογή κανενός"
},
"pdf": {
"export_title": "Εξαγωγή σε PDF",
"export_metadata": "Εξαγωγή σε μεταδεδομένα",
"export_comments": "Εξαγωγή σχολίων",
"fit_to_page": "Εφαρμογή εικόνας στη σελίδα",
"margin": "Περιθώριο",
"millimeter": "χιλ."
},
"share": {
"title": "Κοινοποίηση εγγράφου",
"message": "Ονομάστε την κοινοποίηση αν θέλετε να κοινοποιήσετε πολλές φορές το ίδιο έγγραφο.",
"submit": "Κοινοποίηση"
}
},
"file": {
"view": {
"previous": "Προηγούμενο",
"next": "Επόμενο",
"not_found": "Δεν βρέθηκε αρχείο"
},
"edit": {
"title": "Επεξεργασία αρχείου",
"name": "Όνομα αρχείου"
},
"versions": {
"title": "Ιστορικό εκδοχών",
"filename": "Όνομα αρχείου",
"mimetype": "Είδος",
"create_date": "Ημερομηνία δημιουργίας",
"version": "Εκδοχή"
}
},
"tag": {
"new_tag": "Νέα ετικέτα",
"search": "Αναζήτηση",
"default": {
"title": "Ετικέτες",
"message_1": "<strong>Οι ετικέτες</strong> είναι καρτέλες σχετιζόμενες με έγγραφα.",
"message_2": "Ένα έγγραφο που μπορεί να έχει πολλές ετικέτες, και μια ετικέτα μπορεί να εφαρμοστεί σε πολλά έγγραφα.",
"message_3": "Χρησιμοποιώντας το κουμπί <span class=\"fas fa-pencil-alt\"></span>, μπορεί να γίνει επεξεργασία των αδειών σε μια ετικέτα.",
"message_4": "Αν μια ετικέτα μπορεί να διαβαστεί από έναν άλλο χρήστη ή ομάδα, τα σχετιζόμενα έγγραφα μπορούν επίσης να διαβαστούν από αυτά τα άτομα.",
"message_5": "Για παράδειγμα, βάλε στα έγγραφα της εταιρείας σου μια ετικέτα <span class=\"label label-info\">MyCompany</span> και πρόσθεσε την άδεια <strong>Μπορεί να διαβαστεί</strong> σε μια ομάδα <span class=\"btn btn-default\">εργαζομένων</span>"
},
"edit": {
"delete_tag_title": "Διαγραφή ετικέτας",
"delete_tag_message": "Θέλεις πραγματικά να διαγράψεις αυτή την ετικέτα;",
"name": "Όνομα",
"color": "Χρώμα",
"parent": "Γονέας",
"info": "Οι άδειες σε αυτή την ετικέτα θα εφαρμοστούν επίσης και στα έγγραφα με ετικέτα <span class=\"label label-info\" ng-style=\"{ 'background': color }\">{{ name }}</span>",
"circular_reference_title": "Κυκλική αναφορά",
"circular_reference_message": "Η ιεραρχία των μητρικών ετικετών κάνει κύκλο, παρακαλούμε επέλεξε διαφορετικό γονέα."
}
},
"group": {
"profile": {
"members": "Μέλη",
"no_members": "Χωρίς μέλος",
"related_links": "Σχετικοί συνδέσμοι",
"edit_group": "Επεξεργασία {{ name }} ομάδας"
}
},
"user": {
"profile": {
"groups": "Ομάδες",
"quota_used": "Χρησημοποιημένο ποσοστό",
"percent_used": "{{ percent | number: 0 }}% Χρησιμοποιημένο",
"related_links": "Σχετικοί συνδέσμοι",
"document_created": "Έγγραφα που δημιουργήθηκαν από {{ username }}",
"edit_user": "Επεξεργασία {{ username }} χρήστη"
}
},
"usergroup": {
"search_groups": "Αναζήτηση σε ομάδες",
"search_users": "Αναζήτηση σε χρήστες",
"you": "Είσαι εσύ!",
"default": {
"title": "Χρήστες & Ομάδες",
"message": "Εδώ μπορείς να δεις πληροφορίες για χρήστες και ομάδες."
}
},
"settings": {
"menu_personal_settings": "Προσωπικές ρυθμίσεις",
"menu_user_account": "Λογαριασμός χρήστη",
"menu_two_factor_auth": "Επαλήθευση δύο σταδίων",
"menu_opened_sessions": "Ανοιχτές περιόδοι",
"menu_file_importer": "Εισαγωγέας αρχείων όγκου",
"menu_general_settings": "Γενικές ρυθμίσεις",
"menu_workflow": "Ροή εργασίας",
"menu_users": "Χρήστες",
"menu_groups": "Ομάδες",
"menu_vocabularies": "Λεξιλόγια",
"menu_configuration": "Διαμόρφωση",
"menu_inbox": "Σάρωση εισερχομένων",
"menu_ldap": "Επαλήθευση LDAP",
"menu_metadata": "Μεταδεδομένα προσαρμοσμένων χαρακτηριστικών",
"menu_monitoring": "Έλεγχος",
"ldap": {
"title": "Επαλήθευση LDAP",
"enabled": "Ενεργοποίηση επαλήθευσης LDAP",
"host": "LDAP όνομα εξυπηρετητή",
"port": "LDAP θύρα (389 από προεπιλογή)",
"admin_dn": "Διαχειριστής DN",
"admin_password": "Κωδικός Διαχειριστή",
"base_dn": "Βάση αναζήτησης DN",
"filter": "Φίλτρο αναζήτησης (πρέπει να περιέχει ΟΝΟΜΑ ΧΡΗΣΤΗ, πχ. \"(uid=USERNAME)\")",
"default_email": "Προεπιλεγμένο email για χρήστη LDAP",
"default_storage": "Προεπιλεγμένη αποθήκευση για χρήστη LDAP",
"saved": "Η διαμόρφωση LDAP αποθηκεύτηκε με επιτυχία"
},
"user": {
"title": "Διοίκηση χρηστών",
"add_user": "Προσθήκη χρήστη",
"username": "Όνομα χρήστη",
"create_date": "Ημερομηνία δημιουργίας",
"totp_enabled": "Η επαλήθευση δύο σταδίων ενεργοποιήθηκε για αυτό το λογαριασμό",
"edit": {
"delete_user_title": "Διαγραφή χρήστη",
"delete_user_message": "Θέλεις πραγματικά να διαγράψεις αυτό το χρήστη; Όλα τα σχετικά έγγραφα, αρχεία και ετικέτες θα διαγραφούν",
"user_used_title": "Χρήστης σε χρήση",
"user_used_message": "Αυτός ο χρήστης χρησιμοποιείται στη ροή εργασίας \"{{ name }}\"",
"edit_user_failed_title": "Ο χρήστης υπάρχει ήδη",
"edit_user_failed_message": "Αυτό το όνομα χρήστη έχει παρθεί ήδη από έναν άλλο χρήστη",
"edit_user_title": "Επαξεργασία \"{{ username }}\"",
"add_user_title": "Προσθήκη χρήστη",
"username": "Όνομα χρήστη",
"email": "E-mail",
"groups": "Ομάδες",
"storage_quota": "Ποσοστό αποθήκευσης",
"storage_quota_placeholder": "Ποσοστό αποθήκευσης (σε MB)",
"password": "Κωδικός",
"password_confirm": "Κωδικός (επιβεβαίωση)",
"disabled": "Απενεργοποίηση χρήστη",
"password_reset_btn": "Αποστολή ενός email για επαναφορά κωδικού αυτού το χρήστη",
"password_lost_sent_title": "Το email επαναφοράς κωδικού έχει σταλεί",
"password_lost_sent_message": "Ένα email επαναφοράς κωδικού έχει σταλεί στον/στην <strong>{{ username }}</strong>",
"disable_totp_btn": "Απενεργοποίηση επαλήθευσης δύο-σταδίων για αυτό το χρήστη",
"disable_totp_title": "Απενεργοποίηση επαλήθευσης δύο σταδίων",
"disable_totp_message": "Είσαι σίγουρος πως θέλεις να απενεργοποιήσεις την επαλήθευση δύο σταδίων για αυτό το χρήστη;"
}
},
"workflow": {
"title": "Διαμόρφωση ροής εργασίας",
"add_workflow": "Προσθήκηροής εργασίας",
"name": "Όνομα",
"create_date": "Ημερομηνία δημιουργίας",
"edit": {
"delete_workflow_title": "Διαγραφή ροής εργασίας",
"delete_workflow_message": "Θέλεις πραγματικά να διαγράψεις αυτή τη ροή εργασίας; Οι τρέχουσες ροές εργασίας δεν θα διαγραφούν",
"edit_workflow_title": "Επεξεργασία \"{{ name }}\"",
"add_workflow_title": "Προσθήκη ροής εργασίας",
"name": "Όνομα",
"name_placeholder": "Όνομα βήματος ή περιγραφή",
"drag_help": "Σύρε και άφησε για επανάληψη παραγγελίας βήματος",
"type": "Είδος βήματος",
"type_approve": "Έγκριση",
"type_validate": "Επιβεβαίωση",
"target": "Αναθέτηκε σε",
"target_help": "<strong>Έγκριση:</strong> Αποδοχή ή απόρριψη αξιολόγησης<br/><strong>Επιβεβαίωση:</strong> Αξιολόγηση και συνέχιση της ροής εργασίας",
"add_step": "Προσθήκη βήματος ροής εργασίας",
"actions": "Τι γίνεται μετά;",
"remove_action": "Αφαίρεση ενέργειας",
"acl_info": "Μόνο χρήστες και ομάδες που καθορίζοντε εδώ θα είναι σε θέση να ξεκινήσουν αυτή τη ροή εργασίας σε ένα έγγραφο"
}
},
"security": {
"enable_totp": "Ενεργοποίηση επαλήθευσης δύο σταδίων",
"enable_totp_message": "Βεβαιώσου πως έχει μια εφαρμογή συμβατή με TOTP στο κινητό σου έτοιμη για προσθήκη ενός νέου λογαριασμού",
"title": "Επαλήθευση δύο σταδίων",
"message_1": "Η επαλήθευση δύο σταδίων σου επιτρέπει να προσθέσεις ένα στρώμα ασφάλειας στο <strong>{{ appName }}</strong> λογαριασμό σου.<br/>Πριν την ενεργοποίηση αυτής της λειτουργίας, βαβαιώσου πως έχεις μια εφαρμογή συμβατή με TOTP στο κινητό σου:",
"message_google_authenticator": "Για Android, iOS, και Blackberry: <a href=\"https://support.google.com/accounts/answer/1066447\" target=\"_blank\">Google Authenticator</a>",
"message_duo_mobile": "Για Android και iOS: <a href=\"https://guide.duo.com/third-party-accounts\" target=\"_blank\">Duo Mobile</a>",
"message_authenticator": "Για Τηλέφωνο Windows: <a href=\"https://www.microsoft.com/en-US/store/apps/Authenticator/9WZDNCRFJ3RJ\" target=\"_blank\">Authenticator</a>",
"message_2": "Αυτές οι εφαρμογές δημιουργούν αυτόματα ένα κωδικό επιβεβαίωσης ο οποίος αλλάζει μετά από κάποια χρονική περιόδο.<br/>Θα πρέπει να εισάγεις αυτόν τον κωδικό επιβεβαίωσης κάθε φορά που κάνεις σύνδεστη στο <strong>{{ appName }}</strong>.",
"secret_key": "Το μυστικό κλειδί σου είναι: <strong>{{ secret }}</strong>",
"secret_key_warning": "Διαμόρφωσε την TOTP εφαρμογή στο κινητό σου με αυτό το μυστικό κλειδί τώρα, δεν θα μπορείς να έχεις πρόσβαση αργότερα.",
"totp_enabled_message": "Η επαλήθευση δύο σταδίων έχει ενεργοποιηθεί στο λογαριασμό σου.<br/>Κάθε φορά που κάνεις σύνδεση στο <strong>{{ appName }}</strong>, θα σου ζητείται ο κωδικός επαλήθευσης από τη διαμορφωμένη εφαρμογή στο κινητό σου.<br/>Αν χάσεις το τηλέφωνο σου, δεν θα μπορείς να κάνεις σύνδεση στο λογαριασμό σου αλλά οι ενεργές περιόδοι θα σου επιτρέπουν να regenerate a secrey key.",
"disable_totp": {
"disable_totp": "Απενεργοποίηση επαλήθευσης δύο σταδίων",
"message": "Ο λογαριασμός σου δεν θα προστατευεται από την επαλήθευση δύο σταδίων πλέον.",
"confirm_password": "Επιβεβαίωση του κωδικού σου",
"submit": "Απενεργοποίηση επαλήθευσης δύο σταδίων"
},
"test_totp": "Παρακαλούμε συμπλήρωσε τον κωδικό επαλήθευσης που εμφανίζεται στο τηλέφωνο σου :",
"test_code_success": "Κωδικός επαλήθευσης OK",
"test_code_fail": "Αυτός ο κωδικός δεν είναι έγκυρος, παρακαλούμε έλεγξε ξανά πως το τηλέφωνο έχει διαμορφωθεί σωστά ή απενεργοποίησε την επαλήθευση δύο σταδίων"
},
"group": {
"title": "Διαχείριση ομάδων",
"add_group": "Προσθήκη ομάδας",
"name": "Όνομα",
"edit": {
"delete_group_title": "Διαγραφή ομάδας",
"delete_group_message": "Θέλεις πραγματικά να διαγράψεις αυτή την ομάδα;",
"edit_group_failed_title": "Η ομάδα υπάρχει ήδη",
"edit_group_failed_message": "Αυτό το όνομα ομάδας έχει ήδη παρθεί από μια άλλη ομάδα",
"group_used_title": "Ομάδα σε χρήση",
"group_used_message": "Αυτή η ομάδα χρησιμοποιείται στη ροή εργασίας \"{{ name }}\"",
"edit_group_title": "Επεξεργασία \"{{ name }}\"",
"add_group_title": "Προσθήκη ομάδας",
"name": "Όνομα",
"parent_group": "Ομάδα γονέας",
"search_group": "Αναζήτηση ομάδας",
"members": "Μέλη",
"new_member": "Νέο μέλος",
"search_user": "Αναζήτηση χρήστη"
}
},
"account": {
"title": "Λογαριασμός χρήστη",
"password": "Κωδικός",
"password_confirm": "Κωδικός (επιβεβαίωση)",
"updated": "Ο λογαριασμός ενημερώθηκε επιτυχώς"
},
"config": {
"title_guest_access": "Πρόσβαση επισκέπτη",
"message_guest_access": "Η πρόσβαση επισκέπτη με την οποία μπορεί ο καθένα να έχει πρόσβαση στο {{ appName }} χωρίς κωδικό.<br/>Όπως ένας κανονικός χρήστης, ο χρήστης επισκέπτης μπορεί μόνο ν αέχει πρόσβαση στα έγγραφα του και σε αυτά που είναι προσβάσιμα μέσω αδειών.<br/>",
"enable_guest_access": "Ενεργοποίηση πρόσβασης επισκέπτη",
"disable_guest_access": "Απενεργοποίηση πρόσβασης επισκέπτη",
"title_theme": "Προσαρμογή θέματος",
"title_general": "Γενική διαμόρφωση",
"default_language": "Προεπιλεγμένη γλώσσα για νέα έγγραφα",
"application_name": "Όνομα εφαρμογής",
"main_color": "Κεντρικό χρώμα",
"custom_css": "Προσαρμοσμένο CSS",
"custom_css_placeholder": "Προσαρμοσμένο CSS για προσθήκη μετά το κεντρικό φύλλο είδους",
"logo": "Λογότυπο (τετράγωνο μέγεθος)",
"background_image": "Εικόνα φόντου",
"uploading_image": "Ανέβασμα εικόνας...",
"title_smtp": "Διαμόρφωση Email",
"smtp_hostname": "Όνομα εξυπηρετητή SMTP",
"smtp_port": "SMTP θύρα",
"smtp_from": "e-mail αποστολέα",
"smtp_username": "SMTP όνομα χρήστη",
"smtp_password": "SMTP κωδικός",
"smtp_updated": "SMTP διαμόρφωση ενημερώθηκε επιτυχώς",
"webhooks": "Διαδικτυακοί συνδέσμοι",
"webhooks_explain": "Οι διαδικτυακοί συνδέσμοι θα κληθούν όταν γίνει ένα συγκεκριμένο γεγονός. Το URL θα γίνει ανάρτηση με ένα ωφέλημο φορτίο JSON που περιέχει το όνομα του γεγονότος και τηνταυτότητα της πηγής.",
"webhook_event": "Γεγονός",
"webhook_url": "URL",
"webhook_create_date": "Ημερομηνία δημιουργίας",
"webhook_add": "Προσθήκη διαδικτυακού συνδέσμου"
},
"metadata": {
"title": "Προσαρμοσμένη διαμόρφωση μεταδεδομένων",
"message": "Εδώ μπορείς να προσθέσεις προσαρμοσμένα μεταδεδομένα στα έγγραφα σου όπως εσωτερική αναγνώριση ή ημερομηνία λήξης. Παρακαλούμε σημείωσε πως το είδος των μεταδεδομένων δεν μπορεί νααλλάξει μετά τη δημιουργία.",
"name": "Όνομα μεταδεδομένων",
"type": "Είδος μεταδεδομένων"
},
"inbox": {
"title": "Σάρωση εισερχομένων",
"message": "Ενεργοποιώντας αυτή τη λειτουργία, το σύστημα θα σαρώσει τα συγκεκριμένα εισερχόμενα κάθε λεπτό για <strong>αδιάβαστα</strong> emails και αυτόματα θα τα εισάγει.<br/>Μετά την εισαγωγή ενός email, θα σημειώνεται ως διαβασμένο.<br/>Οι ρυθμίσεις διαμόρφωσης για <a href=\"https://support.google.com/mail/answer/7126229?hl=en\" target=\"_blank\">Gmail</a>, <a href=\"https://support.office.com/en-us/article/pop-imap-and-smtp-settings-for-outlook-com-d088b986-291d-42b8-9564-9c414e2aa040\" target=\"_blank\">Outlook.com</a>, <a href=\"https://help.yahoo.com/kb/SLN4075.html\" target=\"_blank\">Yahoo</a>.",
"enabled": "Ενεργοποίηση σάρωσης εισερχομένων",
"hostname": "IMAP όνομα εξυπηρετητή",
"port": "IMAP θύρα (143 ή 993)",
"username": "IMAP όνομα χρήστη",
"password": "IMAP κωδικός",
"folder": "IMAP φάκελο",
"tag": "Ετικέτα που προστέθηκε σε ειχερχόμενα έγγραφα",
"test": "Δοκιμή μαραμέτρων",
"last_sync": "Τελευταίος συγχρονισμός: {{ data.date | date: 'medium' }}, {{ data.count }} μήνυμα εισήχθη",
"test_success": "Η σύνδεση στα εισερχόμενα είναι επιτυχής ({{ count }} <strong>αδιάβαστο</strong> μήνυμα",
"test_fail": "Προέκυψε ένα σφάλμα κατά τη σύνδεση στα εισερχόμενα, παρακαλούμε έλεγξε τις παραμέτρους",
"saved": "Η διαμόρφωση IMAP αποθηκεύτηκε επιτυχώς",
"autoTagsEnabled": "Αυτόματη προσθήκη επτικετών από τη γραμμή θέματος με το σήμα #",
"deleteImported": "Διαγραφή μηνυματος από το κουτί αλληλογραφίας μετά την εισαγωγή"
},
"monitoring": {
"background_tasks": "Εργασίες φόντου",
"queued_tasks": "Υπάρχουν για την ώρα {{ count }} εργασίες σε σειρά.",
"queued_tasks_explain": "Επεξεργασία αρχείου, δημιουγία thumbnail, ενημέρωση ευρετηρίου, αναγνώρηση οπτικών χαρακτήρων είναι εργασίες φόντου. Ένα μεγάλο ποσό των μη επεξεργασμένων εργασιών θα έχει ως αποτέλεσμα τα μη ολοκληρωμένα αποτελέσματα αναζήτησης.",
"server_logs": "Αρχεία καταγραφής διακομιστή",
"log_date": "Ημερομηνία",
"log_tag": "Ετικέτα",
"log_message": "Μήνυμα",
"indexing": "Καταλογογράφηση",
"indexing_info": "Αν προσέξεις αποκλίσεις στα αποτελέσματα αναζήτησης, μπορείς να δοκιμάσεις να κάνεις μια πλήρη καταλογογράφηση ξανά. Τα αποτελέσματα αναζήτησης θα είναι μη ολοκληρωμένα μέχρι να γίνει αυτή η διεργασία.",
"start_reindexing": "Έναρξη πλήρους καταλογογράφησης",
"reindexing_started": "Η επανάληψη καταλογογράφησης ξεκίνησε, παρακαλούμε περίμενε μέχρι να μην υπάρχουν άλλες εργασίες φόντου."
},
"session": {
"title": "Ανοιχτές περίοδοι",
"created_date": "Ημερομηνία δημιουργίας",
"last_connection_date": "Ημερομηνία τελευταίας σύνδεσης",
"user_agent": "Από",
"current": "Τρέχουσα",
"current_session": "Αυτή είναι η τρέχουσα περίοδος",
"clear_message": "Όλες οι άλλες συσκευές που είναι συνδεδεμένες σε αυτό το λογαριασμό θα αποσυνδεθούν",
"clear": "Καθαρισμός όλων των άλλων περιόδων"
},
"vocabulary": {
"title": "Καταχωρήσεις λεξιλογίου",
"choose_vocabulary": "Επέλεξε ένα λεξιλόγιο για επεξεργασία",
"type": "Είδος",
"coverage": "Κάλυψη",
"rights": "Δικαιώματα",
"value": "Αξία",
"order": "Παραγγελία",
"new_entry": "Νέα καταχώρηση"
},
"fileimporter": {
"title": "Εισαγωγέας όγκου αρχείων",
"advanced_users": "Για προχωρημένους χρήστες!",
"need_intro": "Αν θέλεις να:",
"need_1": "Εισάγεις ένα ευρετήριο αρχείων με τη μια",
"need_2": "Σαρώσεις ένα ευρετήριο για νέα αρχεία και να τα εισάγεις",
"line_1": "Πήγαινε στο <a href=\"https://github.com/sismics/docs/releases\">sismics/docs/releases</a> και κατέβασε το εγαλείο εισαγωγής αρχείων για το σύστημα σου.",
"line_2": "Ακολούθησε τις <a href=\"https://github.com/sismics/docs/tree/master/docs-importer\">οδηγίες εδώ</a> για να χρησιμοποιήσεις αυτό το εργαλείο.",
"line_3": "Τα αρχεία σου θα εισηχθούν σε έγγραφα σύμφωνα με τη διαμόρφωση εισαγωγής αρχείου.",
"download": "Κατέβασε",
"instructions": "Οδηγίες"
}
},
"feedback": {
"title": "Δώσε μας ανατροφοδότηση",
"message": "Οποιαδήποτε πρόταση ή ερώτηση σχετικά με το Teedy? Σε ακούμε!",
"sent_title": "Η ανατροφοδότηση έχει σταλεί",
"sent_message": "Ευχαριστούμε για την ανατροφοδότηση! Θα μας βοηθήσει να κάνουμε το Teedy ακόμα καλύτερο."
},
"import": {
"title": "Εισάγει",
"error_quota": "Το όριο έχει φτάσει, επικοινώνησε με το διαχειριστή σου για να αυξήσεις το ποσοστό σου",
"error_general": "Προέκυψε ένα σφάλμα κατά την προσπάθεια εισαγωγής του αρχείου σου, παρακαλούμε βεβαιώσου πως είναι ένα έγκυρο αρχείο EML"
},
"app_share": {
"main": "Ζήτησε ένα σύνδεσμο κοινοποιημένου εγγράφου για να λάβεις πρόσβαση",
"403": {
"title": "Μη εγκεκριμένο",
"message": "Το έγγραφο που προσπαθείς να δεις δεν είναι κοινό πλέον"
}
},
"directive": {
"acledit": {
"acl_target": "Για",
"acl_permission": "ʼδεια",
"add_permission": "Προσθήκη άδειας",
"search_user_group": "Αναζήτηση χρήστη ή ομάδας"
},
"auditlog": {
"log_created": "δημιουργήθηκε",
"log_updated": "ενημερώθηκε",
"log_deleted": "διαγράφηκε",
"Acl": "ACL",
"Comment": "Σχόλιο",
"Document": "Έγγραφο",
"File": "Αρχείο",
"Group": "Ομάδα",
"Route": "Ροή εργασίας",
"RouteModel": "Μοντέλο ροής εργασίας",
"Tag": "Ετικέτα",
"User": "Χρήστης",
"Webhook": "Διαδικτυακός σύνδεσμος"
},
"selectrelation": {
"typeahead": "Συμπλήρωσε έναν τίτλο εγγράφου"
},
"selecttag": {
"typeahead": "Συμπλήρωσε μια ετικέτα"
},
"datepicker": {
"current": "Σήμερα",
"clear": "Καθαρισμός",
"close": "Έγινε"
}
},
"filter": {
"filesize": {
"mb": "MB",
"kb": "kB"
}
},
"acl": {
"READ": "Μπορεί να διαβάσει",
"READWRITE": "Μπορεί να γράψει",
"WRITE": "Μπορεί να γράψει",
"USER": "Χρήστης",
"GROUP": "Ομάδα",
"SHARE": "Κοινό"
},
"workflow_type": {
"VALIDATE": "Επαλήθευση",
"APPROVE": "Αποδοχή"
},
"workflow_transition": {
"APPROVED": "Εγκρίθηκε",
"REJECTED": "Απορρίφθηκε",
"VALIDATED": "Επαληθεύτηκε"
},
"validation": {
"required": "Απαιτείται",
"too_short": "Πολύ μικρό",
"too_long": "Πολύ μεγάλο",
"email": "Πρέπει να είναι ένα έγκυρο e-mail",
"password_confirm": "Ο κωδικός και η επιβεβαίωση κωδικού πρέπει να ταιριάζουν",
"number": "Αριθμός που απαιτείται",
"no_space": "Κενά και άνω κάτω τελείες δεν επιτρέπονται",
"alphanumeric": "Μόνο γράμματα και αριθμοί επιτρέπονται"
},
"action_type": {
"ADD_TAG": "Προσθήκη ετικέτας",
"REMOVE_TAG": "Αφαίρεση ετικέτας",
"PROCESS_FILES": "Επεξεργασία αρχείων"
},
"pagination": {
"previous": "Προηγούμενο",
"next": "Επόμενο",
"first": "Πρώτο",
"last": "Τελευταίο"
},
"onboarding": {
"step1": {
"title": "Όνομα;",
"description": "Αν είναι η πρώτη σου φορά στο Teedy, κάνε κλικ στο κουμπί Επόμενο, αλλιώς μπορείς να κάνεις κλείσιμο."
},
"step2": {
"title": "Έγγραφα",
"description": "Το Teedy είναι οργανωμένο σε έγγραφα και κάθε έγγραφο περιέχει πολλαπλά αρχεία."
},
"step3": {
"title": "Αρχεία",
"description": "Μπορείς να προσθέσεις αρχεία μετά τη δημιουργία ενός εγγράφου ή πριν τη χρήση αυτής της περιοχής γρήγορου ανεβάσματος."
},
"step4": {
"title": "Αναζήτηση",
"description": "Αυτός είναι ο κετρνικός τρόπος να βρεις τα έγγραφα σου. Υπάρχει επίσης μια προχωρημένη αναζήτηση με το κουμπί μεγένθυσης."
},
"step5": {
"title": "Ετικέτες",
"description": "Τα εγγραφα μπορούν να οργανωθούν σε ετικέτες (που είναι σαν σούπες φάκελοι). Δημιούργησε του εδώ."
}
},
"yes": "Ναι",
"no": "Όχι",
"ok": "OK",
"cancel": "Ακύρωση",
"share": "Κοινοποίηση",
"unshare": "Κατάργηση κοινοποίησης",
"close": "Κλείσιμο",
"add": "Προσθήκη",
"open": "ʼνοιγμα",
"see": "Δες",
"save": "Αποθήκευση",
"export": "Εξαγωγή",
"edit": "Επεξεργασία",
"delete": "Διαγραφή",
"rename": "Μετονομασία",
"download": "Κατέβασμα",
"loading": "Φόρτωση...",
"send": "Αποστολή",
"enabled": "Ενεργοποιημένο",
"disabled": "Απενενργοποιημένο"
}

View File

@@ -3,7 +3,7 @@
"username": "Username",
"password": "Password",
"validation_code_required": "A validation code is required",
"validation_code_title": "You have activated the two-factor authentication on your account. Please enter a validation code generated by the phone app your configured.",
"validation_code_title": "You have activated the two-factor authentication on your account. Please enter a validation code generated by the phone app you have configured.",
"validation_code": "Validation code",
"remember_me": "Remember me",
"submit": "Sign in",
@@ -278,8 +278,22 @@
"menu_vocabularies": "Vocabularies",
"menu_configuration": "Configuration",
"menu_inbox": "Inbox scanning",
"menu_ldap": "LDAP authentication",
"menu_metadata": "Custom metadata",
"menu_monitoring": "Monitoring",
"ldap": {
"title": "LDAP authentication",
"enabled": "Enable LDAP authentication",
"host": "LDAP hostname",
"port": "LDAP port (389 by default)",
"admin_dn": "Admin DN",
"admin_password": "Admin password",
"base_dn": "Base search DN",
"filter": "Search filter (must contains USERNAME, eg. \"(uid=USERNAME)\")",
"default_email": "Default email for LDAP user",
"default_storage": "Default storage for LDAP user",
"saved": "LDAP configuration saved successfully"
},
"user": {
"title": "Users management",
"add_user": "Add a user",
@@ -427,12 +441,15 @@
"port": "IMAP port (143 or 993)",
"username": "IMAP username",
"password": "IMAP password",
"folder": "IMAP folder",
"tag": "Tag added to imported documents",
"test": "Test the parameters",
"last_sync": "Last synchronization: {{ data.date | date: 'medium' }}, {{ data.count }} message{{ data.count > 1 ? 's' : '' }} imported",
"test_success": "The connection to the inbox is successful ({{ count }} <strong>unread</strong> message{{ count > 1 ? 's' : '' }})",
"test_fail": "An error occurred while connecting to the inbox, please check the parameters",
"saved": "IMAP configuration saved successfully"
"saved": "IMAP configuration saved successfully",
"autoTagsEnabled": "Automatically add tags from subject line marked with #",
"deleteImported": "Delete message from mailbox after import"
},
"monitoring": {
"background_tasks": "Background tasks",
@@ -562,7 +579,8 @@
"email": "Must be a valid e-mail",
"password_confirm": "Password and password confirmation must match",
"number": "Number required",
"no_space": "Spaces are not allowed"
"no_space": "Spaces and colons are not allowed",
"alphanumeric": "Only letters and numbers are allowed"
},
"action_type": {
"ADD_TAG": "Add a tag",
@@ -617,4 +635,4 @@
"send": "Send",
"enabled": "Enabled",
"disabled": "Disabled"
}
}

View File

@@ -416,6 +416,7 @@
"port": "Puerto IMAP (143 o 993)",
"username": "Usuario IMAP",
"password": "Contraseña IMAP",
"folder": "Carpeta IMAP",
"tag": "Etiqueta añadida a documentos importado",
"test": "Comprobar parámetros",
"last_sync": "Última sincronización: {{ data.date | date: 'medium' }}, {{ data.count }} mensaje{{ data.count > 1 ? 's' : '' }} importado{{ data.count > 1 ? 's' : '' }}",

Some files were not shown because too many files have changed in this diff Show More