mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-20 16:52:41 +01:00
Compare commits
262 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76295b73c8 | ||
|
|
8c00769757 | ||
|
|
773d0e9d20 | ||
|
|
d14f596479 | ||
|
|
700ed42ce5 | ||
|
|
dc2369c71e | ||
|
|
5fc760f6ad | ||
|
|
ffb5d3e790 | ||
|
|
512947e0d0 | ||
|
|
9e69a09a19 | ||
|
|
b447a69dae | ||
|
|
d52e6b5881 | ||
|
|
6cff19358a | ||
|
|
a6d476f953 | ||
|
|
aba73174ab | ||
|
|
83d43d931c | ||
|
|
64cebaba77 | ||
|
|
07535c26a6 | ||
|
|
aab1dcf8e6 | ||
|
|
4b88de9316 | ||
|
|
84c111ac7c | ||
|
|
2feeb1c868 | ||
|
|
17000da97e | ||
|
|
5b09cbf1ac | ||
|
|
07088c94e7 | ||
|
|
1da5e7ccd7 | ||
|
|
b9956e38b8 | ||
|
|
36879dd7da | ||
|
|
099ea63740 | ||
|
|
615defa84a | ||
|
|
3eeeb01ad1 | ||
|
|
73f6d79925 | ||
|
|
b0f5d9b55f | ||
|
|
50069c7611 | ||
|
|
c86694ab8f | ||
|
|
478d5e2a3a | ||
|
|
e7b766906d | ||
|
|
c5435df6f9 | ||
|
|
e8f4cd9fec | ||
|
|
378d695a24 | ||
|
|
a4b16f7f09 | ||
|
|
1fe3a614c9 | ||
|
|
773e393f55 | ||
|
|
87626589a3 | ||
|
|
01784a9d1f | ||
|
|
f99323f9b3 | ||
|
|
83ad99215f | ||
|
|
958d59a0ff | ||
|
|
de8a68c70d | ||
|
|
5f87d5b1ac | ||
|
|
c2ea880dad | ||
|
|
7eba4254e6 | ||
|
|
76bb3eae9d | ||
|
|
3da656c08b | ||
|
|
b6dc3eb1a2 | ||
|
|
fefa65941b | ||
|
|
74d75c6e1f | ||
|
|
01ed3eeecd | ||
|
|
9a3b9b84bc | ||
|
|
90a1ffa2ac | ||
|
|
5442aa5e07 | ||
|
|
0ab604d468 | ||
|
|
0b178b46f2 | ||
|
|
d12bde2b1e | ||
|
|
96a771e7ac | ||
|
|
3e6b80d1cf | ||
|
|
4d7d196a3c | ||
|
|
4e1f6277c6 | ||
|
|
626c4dd5d6 | ||
|
|
c8bd800b9f | ||
|
|
0fa03d8bb0 | ||
|
|
22606f01d2 | ||
|
|
3c2e535117 | ||
|
|
7f612bc371 | ||
|
|
cc2332a83a | ||
|
|
c7892cb9e2 | ||
|
|
5bd2d9b344 | ||
|
|
81f8b365e9 | ||
|
|
8ab9cf1417 | ||
|
|
b7cfdebad5 | ||
|
|
0447a7e6b3 | ||
|
|
6d67ee8106 | ||
|
|
2d7058329c | ||
|
|
9e58baa574 | ||
|
|
6d8cb9cc08 | ||
|
|
5cfccab671 | ||
|
|
3953e36921 | ||
|
|
7163df6d46 | ||
|
|
5f86253b94 | ||
|
|
93d0f97cfd | ||
|
|
9732b71f85 | ||
|
|
cf11320789 | ||
|
|
5e326bca12 | ||
|
|
3c52e57a44 | ||
|
|
2002b9d5d3 | ||
|
|
323c70393d | ||
|
|
eabd03dc53 | ||
|
|
3ac82cf76a | ||
|
|
1409d19922 | ||
|
|
bdcd51d533 | ||
|
|
563edb1731 | ||
|
|
cd7013f776 | ||
|
|
783a00ca2f | ||
|
|
e233940f1f | ||
|
|
717a9fb0a3 | ||
|
|
d0a5b4dcd7 | ||
|
|
294f7cf005 | ||
|
|
eb24aa2e68 | ||
|
|
a1c680d14d | ||
|
|
5144b75ed7 | ||
|
|
aeed7c0802 | ||
|
|
2b470e6cdd | ||
|
|
e6870c61ee | ||
|
|
f8ccd5bc22 | ||
|
|
7239aef47f | ||
|
|
91cab91c81 | ||
|
|
ce318bab9a | ||
|
|
b500fa82bb | ||
|
|
388d26fa05 | ||
|
|
9fd3012c27 | ||
|
|
3f36972764 | ||
|
|
9cd5d14708 | ||
|
|
cc82547699 | ||
|
|
a9be442dd1 | ||
|
|
ffbcf25246 | ||
|
|
e9665d7a7a | ||
|
|
0a06467f79 | ||
|
|
31510f6762 | ||
|
|
b298ee0003 | ||
|
|
97b539e1c6 | ||
|
|
6546fba218 | ||
|
|
b97132dbb5 | ||
|
|
aeeb4b87ec | ||
|
|
9a4877cdb0 | ||
|
|
919beda7f5 | ||
|
|
306ecff9c7 | ||
|
|
0c689d492b | ||
|
|
c3de0ccacc | ||
|
|
0d5bc2b29f | ||
|
|
d675f072d6 | ||
|
|
6f30db4877 | ||
|
|
cdcfcc7717 | ||
|
|
71765558fc | ||
|
|
5d3b1a7bcd | ||
|
|
a2a477e4e5 | ||
|
|
f32c0f52b6 | ||
|
|
47fc7db953 | ||
|
|
cc267c0edf | ||
|
|
f5a3ab1f9b | ||
|
|
f6c97b58aa | ||
|
|
7a2928e202 | ||
|
|
f53c98312e | ||
|
|
180505cba4 | ||
|
|
6d6a69e1dd | ||
|
|
5f61e096f9 | ||
|
|
1becde537b | ||
|
|
3c5a96ba5c | ||
|
|
470df57f58 | ||
|
|
185c88fa3e | ||
|
|
79262972aa | ||
|
|
1369091b90 | ||
|
|
b0322b4c03 | ||
|
|
c402ec40a5 | ||
|
|
1964084155 | ||
|
|
4b5f9648b1 | ||
|
|
9d9287cefb | ||
|
|
18ae32f15a | ||
|
|
7cd2662c77 | ||
|
|
4f0730b6f9 | ||
|
|
d7bc74fb2b | ||
|
|
a0b31cfd7e | ||
|
|
33033bc5b1 | ||
|
|
d6daac0762 | ||
|
|
6a5a632a0e | ||
|
|
c64633299a | ||
|
|
f4e173d3fb | ||
|
|
5ff8d1c1cb | ||
|
|
aad40aefbc | ||
|
|
ff77dac5fb | ||
|
|
ced53c2690 | ||
|
|
6f66d50ff6 | ||
|
|
06f88341ba | ||
|
|
2a9a86aaaf | ||
|
|
2409c7f62b | ||
|
|
1b276c5eae | ||
|
|
608bd247af | ||
|
|
e9cdd20dcc | ||
|
|
37dccd4249 | ||
|
|
485d162d82 | ||
|
|
aaf230fc58 | ||
|
|
f9cafae789 | ||
|
|
c758027d4c | ||
|
|
c178d45f73 | ||
|
|
8c9abce633 | ||
|
|
0753b7137f | ||
|
|
9d54001f89 | ||
|
|
00bb005e4e | ||
|
|
60b8650a1a | ||
|
|
ec4eccdf08 | ||
|
|
64c38042a0 | ||
|
|
e09f60e71f | ||
|
|
0e75d76720 | ||
|
|
852624ae7e | ||
|
|
00708608cd | ||
|
|
e339b7d9f0 | ||
|
|
2f958dafae | ||
|
|
3df47ed748 | ||
|
|
c7a02ae870 | ||
|
|
42356cc2a3 | ||
|
|
b796ae36db | ||
|
|
7bd44484be | ||
|
|
607bb45f5f | ||
|
|
90518056cd | ||
|
|
1e52ec42ca | ||
|
|
1e04ee14de | ||
|
|
469f9e8933 | ||
|
|
c49aff5cfc | ||
|
|
ccb94c8a13 | ||
|
|
f285061a76 | ||
|
|
78a6262665 | ||
|
|
8a13799829 | ||
|
|
34059ad99d | ||
|
|
87e881f8e0 | ||
|
|
1823bc6528 | ||
|
|
077beb37b1 | ||
|
|
8182e83846 | ||
|
|
59f62d4a4c | ||
|
|
43c15de55c | ||
|
|
d2144a1fed | ||
|
|
915444b5da | ||
|
|
6387c24c89 | ||
|
|
f01ec9dbe4 | ||
|
|
219fbe5fca | ||
|
|
64db3ed965 | ||
|
|
deec059a38 | ||
|
|
4e57750214 | ||
|
|
3738cb6025 | ||
|
|
0af5a58dbe | ||
|
|
09acca950d | ||
|
|
e04b635c98 | ||
|
|
9bd1b86f6e | ||
|
|
17b49e7ae5 | ||
|
|
7b6ba37667 | ||
|
|
879b702fc1 | ||
|
|
f265b9d19d | ||
|
|
6be55d1837 | ||
|
|
fc6643bd6f | ||
|
|
56d120cd08 | ||
|
|
3e693642b6 | ||
|
|
8fe3f4cf5c | ||
|
|
be14fe548c | ||
|
|
85f3ba6aaa | ||
|
|
35a0e8464a | ||
|
|
040e86ea6d | ||
|
|
8dad143f8d | ||
|
|
bcd41c4d9b | ||
|
|
3bc6e61869 | ||
|
|
3486957447 | ||
|
|
6b5c51bdc5 | ||
|
|
676c8eeefb | ||
|
|
f5a15b23d6 | ||
|
|
6d3b0261b3 |
@@ -34,13 +34,14 @@
|
||||
PassEnv ERROR_PAGE_ADMIN_EMAIL ERROR_PAGE_SHOW_HELP
|
||||
PassEnv DEMO_MODE NO_URL_REWRITE_AVAILABLE FIXER_API_KEY BANNER
|
||||
# In old version the SAML sp private key env, was wrongly named SAMLP_SP_PRIVATE_KEY, keep it for backward compatibility
|
||||
PassEnv SAML_ENABLED SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAML_SP_PRIVATE_KEY SAMLP_SP_PRIVATE_KEY
|
||||
PassEnv TABLE_DEFAULT_PAGE_SIZE
|
||||
PassEnv SAML_ENABLED SAML_BEHIND_PROXY SAML_ROLE_MAPPING SAML_UPDATE_GROUP_ON_LOGIN SAML_IDP_ENTITY_ID SAML_IDP_SINGLE_SIGN_ON_SERVICE SAML_IDP_SINGLE_LOGOUT_SERVICE SAML_IDP_X509_CERT SAML_SP_ENTITY_ID SAML_SP_X509_CERT SAML_SP_PRIVATE_KEY SAMLP_SP_PRIVATE_KEY
|
||||
PassEnv TABLE_DEFAULT_PAGE_SIZE TABLE_PARTS_DEFAULT_COLUMNS
|
||||
|
||||
PassEnv PROVIDER_DIGIKEY_CLIENT_ID PROVIDER_DIGIKEY_SECRET PROVIDER_DIGIKEY_CURRENCY PROVIDER_DIGIKEY_LANGUAGE PROVIDER_DIGIKEY_COUNTRY
|
||||
PassEnv PROVIDER_ELEMENT14_KEY PROVIDER_ELEMENT14_STORE_ID
|
||||
PassEnv PROVIDER_TME_KEY PROVIDER_TME_SECRET PROVIDER_TME_CURRENCY PROVIDER_TME_LANGUAGE PROVIDER_TME_COUNTRY PROVIDER_TME_GET_GROSS_PRICES
|
||||
PassEnv PROVIDER_OCTOPART_CLIENT_ID PROVIDER_OCTOPART_SECRET PROVIDER_OCTOPART_CURRENCY PROVIDER_OCTOPART_COUNTRY PROVIDER_OCTOPART_SEARCH_LIMIT PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS
|
||||
PassEnv PROVIDER_MOUSER_KEY PROVIDER_MOUSER_SEARCH_OPTION PROVIDER_MOUSER_SEARCH_LIMIT PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE
|
||||
|
||||
# For most configuration files from conf-available/, which are
|
||||
# enabled or disabled at a global level, it is possible to
|
||||
|
||||
23
.env
23
.env
@@ -93,6 +93,9 @@ ERROR_PAGE_SHOW_HELP=1
|
||||
|
||||
# The default page size for the part table (set to -1 to show all parts on one page)
|
||||
TABLE_DEFAULT_PAGE_SIZE=50
|
||||
# Configure which columns will be visible by default in the parts table (and in which order).
|
||||
# This is a comma separated list of column names. See documentation for available values.
|
||||
TABLE_PARTS_DEFAULT_COLUMNS=name,description,category,footprint,manufacturer,storage_location,amount
|
||||
|
||||
##################################################################################
|
||||
# Info provider settings
|
||||
@@ -142,6 +145,17 @@ PROVIDER_OCTOPART_SEARCH_LIMIT=10
|
||||
# Set to false to include non authorized offers in the results
|
||||
PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS=1
|
||||
|
||||
# Mouser Provider API V2:
|
||||
# You can get your API key from https://www.mouser.it/api-hub/
|
||||
PROVIDER_MOUSER_KEY=
|
||||
# Filter search results by RoHS compliance and stock availability:
|
||||
# Available options: None | Rohs | InStock | RohsAndInStock
|
||||
PROVIDER_MOUSER_SEARCH_OPTION='None'
|
||||
# The number of results to get from Mouser while searching (please note that this value is max 50)
|
||||
PROVIDER_MOUSER_SEARCH_LIMIT=50
|
||||
# It is recommended to leave this set to 'true'. The option is not really good doumented by Mouser:
|
||||
# Used when searching for keywords in the language specified when you signed up for Search API.
|
||||
PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE='true'
|
||||
|
||||
###################################################################################
|
||||
# SAML Single sign on-settings
|
||||
@@ -149,6 +163,9 @@ PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS=1
|
||||
# Set this to 1 to enable SAML single sign on
|
||||
SAML_ENABLED=0
|
||||
|
||||
# Set to 1, if your Part-DB installation is behind a reverse proxy and you want to use SAML
|
||||
SAML_BEHIND_PROXY=0
|
||||
|
||||
# A JSON encoded array of role mappings in the form { "saml_role": PARTDB_GROUP_ID, "*": PARTDB_GROUP_ID }
|
||||
# The first match is used, so the order is important! Put the group mapping with the most privileges first.
|
||||
# Please not to only use single quotes to enclose the JSON string
|
||||
@@ -200,7 +217,7 @@ APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
|
||||
|
||||
|
||||
# Set the trusted IPs here, when using an reverse proxy
|
||||
#TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
#TRUSTED_PROXIES=127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
#TRUSTED_HOSTS='^(localhost|example\.com)$'
|
||||
|
||||
|
||||
@@ -209,3 +226,7 @@ APP_SECRET=a03498528f5a5fc089273ec9ae5b2849
|
||||
# postgresql+advisory://db_user:db_password@localhost/db_name
|
||||
LOCK_DSN=flock
|
||||
###< symfony/lock ###
|
||||
|
||||
###> nelmio/cors-bundle ###
|
||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
||||
###< nelmio/cors-bundle ###
|
||||
|
||||
10
.github/workflows/assets_artifact_build.yml
vendored
10
.github/workflows/assets_artifact_build.yml
vendored
@@ -21,6 +21,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
@@ -49,7 +57,7 @@ jobs:
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
|
||||
2
.github/workflows/docker_build.yml
vendored
2
.github/workflows/docker_build.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
13
.github/workflows/static_analysis.yml
vendored
13
.github/workflows/static_analysis.yml
vendored
@@ -18,6 +18,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.2'
|
||||
coverage: none
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
|
||||
- name: Get Composer Cache Directory
|
||||
id: composer-cache
|
||||
run: |
|
||||
@@ -48,9 +56,10 @@ jobs:
|
||||
|
||||
- name: Check doctrine mapping
|
||||
run: ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction
|
||||
|
||||
|
||||
# Use the -d option to raise the max nesting level
|
||||
- name: Generate dev container
|
||||
run: ./bin/console cache:clear --env dev
|
||||
run: php -d xdebug.max_nesting_level=1000 ./bin/console cache:clear --env dev
|
||||
|
||||
- name: Run PHPstan
|
||||
run: composer phpstan
|
||||
|
||||
8
.github/workflows/tests.yml
vendored
8
.github/workflows/tests.yml
vendored
@@ -13,12 +13,11 @@ on:
|
||||
jobs:
|
||||
phpunit:
|
||||
name: PHPUnit and coverage Test (PHP ${{ matrix.php-versions }}, ${{ matrix.db-type }})
|
||||
# Ubuntu 20.04 ships MySQL 8.0 which causes problems with login, so we just use ubuntu 18.04 for now...
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: [ '8.1', '8.2' ]
|
||||
php-versions: [ '8.1', '8.2', '8.3' ]
|
||||
db-type: [ 'mysql', 'sqlite' ]
|
||||
|
||||
env:
|
||||
@@ -45,6 +44,7 @@ jobs:
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: pcov
|
||||
ini-values: xdebug.max_nesting_level=1000
|
||||
extensions: mbstring, intl, gd, xsl, gmp, bcmath
|
||||
|
||||
- name: Start MySQL
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
run: composer install --prefer-dist --no-progress
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
@@ -118,6 +118,8 @@ jobs:
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
env_vars: PHP_VERSION,DB_TYPE
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: true
|
||||
|
||||
- name: Test app:clean-attachments
|
||||
run: php bin/console partdb:attachments:clean-unused -n
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -43,3 +43,7 @@ yarn-error.log
|
||||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
|
||||
132
README.md
132
README.md
@@ -1,7 +1,7 @@
|
||||
[](https://scrutinizer-ci.com/g/Part-DB/Part-DB-symfony/?branch=master)
|
||||

|
||||

|
||||
[](https://codecov.io/gh/Part-DB/Part-DB-server)
|
||||
[](https://codecov.io/gh/Part-DB/Part-DB-server)
|
||||

|
||||

|
||||
|
||||
@@ -9,50 +9,60 @@
|
||||

|
||||
[](https://part-db.crowdin.com/part-db)
|
||||
|
||||
**[Documentation](https://docs.part-db.de/)** | **[Demo](https://part-db.herokuapp.com)** | **[Docker Image](https://hub.docker.com/r/jbtronics/part-db1)**
|
||||
**[Documentation](https://docs.part-db.de/)** | **[Demo](https://demo.part-db.de/)** | **[Docker Image](https://hub.docker.com/r/jbtronics/part-db1)**
|
||||
|
||||
# Part-DB
|
||||
Part-DB is an Open-Source inventory managment system for your electronic components.
|
||||
|
||||
Part-DB is an Open-Source inventory management system for your electronic components.
|
||||
It is installed on a web server and so can be accessed with any browser without the need to install additional software.
|
||||
|
||||
The version in this Repository is a complete rewrite of the legacy [Part-DB](https://github.com/Part-DB/Part-DB) (Version < 1.0) based on a modern framework.
|
||||
Currently, it is still missing some (minor) features from the old version (see [UPGRADE.md](https://docs.part-db.de/upgrade_legacy.html)) for more details, but also many huge improvements and advantages compared to the old version.
|
||||
If you start completely new with Part-DB it is recommended that you use the version from this repository, as it is actively developed.
|
||||
The version in this repository is a complete rewrite of the legacy [Part-DB](https://github.com/Part-DB/Part-DB)
|
||||
(Version < 1.0) based on a modern framework and is the recommended version to use.
|
||||
|
||||
If you find a bug, please open an [Issue on Github](https://github.com/Part-DB/Part-DB-server/issues) so it can be fixed for everybody.
|
||||
If you find a bug, please open an [Issue on GitHub,](https://github.com/Part-DB/Part-DB-server/issues) so it can be fixed
|
||||
for everybody.
|
||||
|
||||
## Demo
|
||||
If you want to test Part-DB without installing it, you can use [this](https://part-db.herokuapp.com) Heroku instance.
|
||||
(Or this link for the [German Version](https://part-db.herokuapp.com/de/)).
|
||||
|
||||
If you want to test Part-DB without installing it, you can use [this](https://demo.part-db.de/) Heroku instance.
|
||||
(Or this link for the [German Version](https://demo.part-db.de/de/)).
|
||||
|
||||
You can log in with username: *user* and password: *user*.
|
||||
|
||||
Every change to the master branch gets automatically deployed, so it represents the current development progress and is
|
||||
maybe not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading the page
|
||||
maybe not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
||||
the page
|
||||
for the first time.
|
||||
|
||||
<img src="https://github.com/Part-DB/Part-DB-server/raw/master/docs/assets/readme/part_info.png">
|
||||
<img src="https://github.com/Part-DB/Part-DB-server/raw/master/docs/assets/readme/parts_list.png">
|
||||
|
||||
## Features
|
||||
* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer
|
||||
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files like datasheets or pictures with the parts.
|
||||
|
||||
* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer
|
||||
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files
|
||||
like datasheets or pictures with the parts.
|
||||
* Multi-Language support (currently German, English, Russian, Japanese and French (experimental))
|
||||
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner
|
||||
* User system with groups and detailed (fine granular) permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. Password reset via email can be setuped.
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
* User system with groups and detailed (fine granular) permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups.
|
||||
Password reset via email can be setup.
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service
|
||||
like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
* Import/Export system for parts and datastructure. BOM import for projects from KiCAD is supported.
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions.
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build
|
||||
this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older
|
||||
versions.
|
||||
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface.
|
||||
* MySQL and SQLite supported as database backends
|
||||
* Support for rich text descriptions and comments in parts
|
||||
* Support for multiple currencies and automatic update of exchange rates supported
|
||||
* Powerful search and filter function, including parametric search (search for parts according to some specifications)
|
||||
* Automatic thumbnail generation for pictures
|
||||
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and prices for parts
|
||||
|
||||
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and
|
||||
prices for parts
|
||||
* API to access Part-DB from other applications/scripts
|
||||
|
||||
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or makerspaces, where many users have should have (controlled) access to the shared inventory.
|
||||
@@ -60,69 +70,93 @@ or makerspaces, where many users have should have (controlled) access to the sha
|
||||
Part-DB is also used by small companies and universities for managing their inventory.
|
||||
|
||||
## Requirements
|
||||
* A **web server** (like Apache2 or nginx) that is capable of running [Symfony 5](https://symfony.com/doc/current/reference/requirements.html),
|
||||
this includes a minimum PHP version of **PHP 8.1**
|
||||
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.2.2) database server if you do not want to use SQLite.
|
||||
* Shell access to your server is highly suggested!
|
||||
* For building the client side assets **yarn** and **nodejs** (>= 18.0) is needed.
|
||||
|
||||
|
||||
* A **web server** (like Apache2 or nginx) that is capable of
|
||||
running [Symfony 5](https://symfony.com/doc/current/reference/requirements.html),
|
||||
this includes a minimum PHP version of **PHP 8.1**
|
||||
* A **MySQL** (at least 5.7) /**MariaDB** (at least 10.2.2) database server if you do not want to use SQLite.
|
||||
* Shell access to your server is highly suggested!
|
||||
* For building the client side assets **yarn** and **nodejs** (>= 18.0) is needed.
|
||||
|
||||
## Installation
|
||||
If you want to upgrade your legacy (< 1.0.0) version of Part-DB to this version, please read [this](https://docs.part-db.de/upgrade_legacy.html) first.
|
||||
|
||||
*Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to set up Part-DB via docker is described [here](https://docs.part-db.de/installation/installation_docker.html).
|
||||
If you want to upgrade your legacy (< 1.0.0) version of Part-DB to this version, please
|
||||
read [this](https://docs.part-db.de/upgrade_legacy.html) first.
|
||||
|
||||
**Below you find some very rough outline of the installation process, see [here](https://docs.part-db.de/installation/) for a detailed guide how to install Part-DB.**
|
||||
*Hint:* A docker image is available under [jbtronics/part-db1](https://hub.docker.com/r/jbtronics/part-db1). How to set
|
||||
up Part-DB via docker is described [here](https://docs.part-db.de/installation/installation_docker.html).
|
||||
|
||||
**Below you find some very rough outline of the installation process, see [here](https://docs.part-db.de/installation/)
|
||||
for a detailed guide how to install Part-DB.**
|
||||
|
||||
1. Copy or clone this repository into a folder on your server.
|
||||
2. Configure your webserver to serve from the `public/` folder. See [here](https://symfony.com/doc/current/setup/web_server_configuration.html)
|
||||
for additional information.
|
||||
2. Configure your webserver to serve from the `public/` folder.
|
||||
See [here](https://symfony.com/doc/current/setup/web_server_configuration.html)
|
||||
for additional information.
|
||||
3. Copy the global config file `cp .env .env.local` and edit `.env.local`:
|
||||
* Change the line `APP_ENV=dev` to `APP_ENV=prod`
|
||||
* If you do not want to use SQLite, change the value of `DATABASE_URL=` to your needs (see [here](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url)) for the format.
|
||||
In bigger instances with concurrent accesses, MySQL is more performant. This can not be changed easily later, so choose wisely.
|
||||
* If you do not want to use SQLite, change the value of `DATABASE_URL=` to your needs (
|
||||
see [here](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url))
|
||||
for the format.
|
||||
In bigger instances with concurrent accesses, MySQL is more performant. This can not be changed easily later, so
|
||||
choose wisely.
|
||||
4. Install composer dependencies and generate autoload files: `composer install -o --no-dev`
|
||||
5. If you have put Part-DB into a sub-directory on your server (like `part-db/`), you have to edit the file
|
||||
`webpack.config.js` and uncomment the lines (remove the `//` before the lines) `.setPublicPath('/part-db/build')` (line 43) and
|
||||
`.setManifestKeyPrefix('build/')` (line 44). You have to replace `/part-db` with your own path on line 44.
|
||||
6. Install client side dependencies and build it: `yarn install` and `yarn build`
|
||||
7. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup`
|
||||
8. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**: This steps tamper with your database and could potentially destroy it. So make sure to make a backup of your database.
|
||||
9. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations, after you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be changed after creating price informations).
|
||||
5. Install client side dependencies and build it: `yarn install` and `yarn build`
|
||||
6. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup`
|
||||
7. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and
|
||||
follow the instructions given. During the process the password for the admin is user is shown. Copy it. **Caution**:
|
||||
This steps tamper with your database and could potentially destroy it. So make sure to make a backup of your
|
||||
database.
|
||||
8. You can configure Part-DB via `config/parameters.yaml`. You should check if settings match your expectations, after
|
||||
you installed/upgraded Part-DB. Check if `partdb.default_currency` matches your mainly used currency (this can not be
|
||||
changed after creating price information).
|
||||
Run `php bin/console cache:clear` when you changed something.
|
||||
10. Access Part-DB in your browser (under the URL you put it) and login with user *admin*. Password is the one outputted during DB setup.
|
||||
If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create new users with the admin user and start using Part-DB.
|
||||
9. Access Part-DB in your browser (under the URL you put it) and login with user *admin*. Password is the one outputted
|
||||
during DB setup.
|
||||
If you can not remember the password, set a new one with `php bin/console app:set-password admin`. You can create
|
||||
new users with the admin user and start using Part-DB.
|
||||
|
||||
When you want to upgrade to a newer version, then just copy the new files into the folder
|
||||
and repeat the steps 4. to 7.
|
||||
|
||||
Normally a random password is generated when the admin user is created during inital database creation,
|
||||
however you can set the inital admin password, by setting the `INITIAL_ADMIN_PW` env var.
|
||||
Normally a random password is generated when the admin user is created during initial database creation,
|
||||
however you can set the initial admin password, by setting the `INITIAL_ADMIN_PW` env var.
|
||||
|
||||
You can configure Part-DB to your needs by changing environment variables in the `.env.local` file.
|
||||
You can configure Part-DB to your needs by changing environment variables in the `.env.local` file.
|
||||
See [here](https://docs.part-db.de/configuration.html) for more information.
|
||||
|
||||
### Reverse proxy
|
||||
If you are using a reverse proxy, you have to ensure that the proxies sets the `X-Forwarded-*` headers correctly, or you will get HTTP/HTTPS mixup and wrong hostnames.
|
||||
If the reverse proxy is on a different server (or it cannot access Part-DB via localhost) you have to set the `TRUSTED_PROXIES` env variable to match your reverse proxies IP-address (or IP block). You can do this in your `.env.local` or (when using docker) in your `docker-compose.yml` file.
|
||||
|
||||
If you are using a reverse proxy, you have to ensure that the proxies sets the `X-Forwarded-*` headers correctly, or you
|
||||
will get HTTP/HTTPS mixup and wrong hostnames.
|
||||
If the reverse proxy is on a different server (or it cannot access Part-DB via localhost) you have to set
|
||||
the `TRUSTED_PROXIES` env variable to match your reverse proxies IP-address (or IP block). You can do this in
|
||||
your `.env.local` or (when using docker) in your `docker-compose.yml` file.
|
||||
|
||||
## Donate for development
|
||||
|
||||
If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name).
|
||||
There you will find various methods to support development on a monthly or a one time base.
|
||||
|
||||
## Built with
|
||||
|
||||
* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP
|
||||
* [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme
|
||||
* [Fontawesome](https://fontawesome.com/): Used as icon set
|
||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend Javascript
|
||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend
|
||||
Javascript
|
||||
|
||||
## Authors
|
||||
* **Jan Böhmer** - *Inital work* - [Github](https://github.com/jbtronics/)
|
||||
|
||||
See also the list of [contributors](https://github.com/Part-DB/Part-DB-server/graphs/contributors) who participated in this project.
|
||||
* **Jan Böhmer** - *Initial work* - [GitHub](https://github.com/jbtronics/)
|
||||
|
||||
See also the list of [contributors](https://github.com/Part-DB/Part-DB-server/graphs/contributors) who participated in
|
||||
this project.
|
||||
|
||||
Based on the original Part-DB by Christoph Lechner and K. Jacobs
|
||||
|
||||
## License
|
||||
|
||||
Part-DB is licensed under the GNU Affero General Public License v3.0 (or at your opinion any later).
|
||||
This mostly means that you can use Part-DB for whatever you want (even use it commercially)
|
||||
as long as you publish the source code for every change you make under the AGPL, too.
|
||||
|
||||
@@ -85,6 +85,9 @@ const PLACEHOLDERS = [
|
||||
['[[COMMENT_T]]', 'Comment (plain text)'],
|
||||
['[[LAST_MODIFIED]]', 'Last modified datetime'],
|
||||
['[[CREATION_DATE]]', 'Creation datetime'],
|
||||
['[[IPN_BARCODE_QR]]', 'IPN as QR code'],
|
||||
['[[IPN_BARCODE_C128]]', 'IPN as Code 128 barcode'],
|
||||
['[[IPN_BARCODE_C39]]', 'IPN as Code 39 barcode'],
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -48,6 +48,9 @@ Object.assign( window.CKEDITOR_TRANSLATIONS[ 'de' ].dictionary, {
|
||||
'Comment (plain text)': 'Kommentar (Nur-Text)',
|
||||
'Last modified datetime': 'Zuletzt geändert',
|
||||
'Creation datetime': 'Erstellt',
|
||||
'IPN as QR code': 'IPN als QR Code',
|
||||
'IPN as Code 128 barcode': 'IPN als Code 128 Barcode',
|
||||
'IPN as Code 39 barcode': 'IPN als Code 39 Barcode',
|
||||
|
||||
'Lot ID': 'Lot ID',
|
||||
'Lot name': 'Lot Name',
|
||||
|
||||
@@ -43,7 +43,8 @@ export default class extends Controller
|
||||
const message = this.element.dataset.deleteMessage;
|
||||
const title = this.element.dataset.deleteTitle;
|
||||
|
||||
const form = this.element;
|
||||
//Use event target, to find the form, where the submit button was clicked
|
||||
const form = event.target;
|
||||
const submitter = event.submitter;
|
||||
const that = this;
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
static values = {
|
||||
id: String
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.loadState()
|
||||
this.element.addEventListener('change', () => {
|
||||
this.saveState()
|
||||
});
|
||||
}
|
||||
|
||||
loadState() {
|
||||
let storageKey = this.getStorageKey();
|
||||
let value = localStorage.getItem(storageKey);
|
||||
if (value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === 'true') {
|
||||
this.element.checked = true
|
||||
}
|
||||
if (value === 'false') {
|
||||
this.element.checked = false
|
||||
}
|
||||
}
|
||||
|
||||
saveState() {
|
||||
let storageKey = this.getStorageKey();
|
||||
|
||||
if (this.element.checked) {
|
||||
localStorage.setItem(storageKey, 'true');
|
||||
} else {
|
||||
localStorage.setItem(storageKey, 'false');
|
||||
}
|
||||
}
|
||||
|
||||
getStorageKey() {
|
||||
if (this.hasIdValue) {
|
||||
return 'persistent_checkbox_' + this.idValue
|
||||
}
|
||||
|
||||
return 'persistent_checkbox_' + this.element.id;
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
let tmp = '<div class="row m-0">' +
|
||||
"<div class='col-2 p-0 d-flex align-items-center'>" +
|
||||
"<div class='col-2 p-0 d-flex align-items-center' style='max-width: 80px;'>" +
|
||||
(data.image ? "<img class='typeahead-image' src='" + data.image + "'/>" : "") +
|
||||
"</div>" +
|
||||
"<div class='col-10'>" +
|
||||
|
||||
@@ -66,7 +66,8 @@ export default class extends Controller {
|
||||
};
|
||||
|
||||
this._tomSelect = new TomSelect(this.element, settings);
|
||||
this._tomSelect.sync();
|
||||
//Do not do a sync here as this breaks the initial rendering of the empty option
|
||||
//this._tomSelect.sync();
|
||||
}
|
||||
|
||||
createItem(input, callback) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [ "display", "select" ]
|
||||
|
||||
connect()
|
||||
{
|
||||
this.update();
|
||||
this.selectTarget.addEventListener('change', this.update.bind(this));
|
||||
}
|
||||
|
||||
update()
|
||||
{
|
||||
//If the select value is 0, then we show the input field
|
||||
if( this.selectTarget.value === '0')
|
||||
{
|
||||
this.displayTarget.classList.remove('d-none');
|
||||
}
|
||||
else
|
||||
{
|
||||
this.displayTarget.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
}
|
||||
68
assets/controllers/pages/part_merge_modal_controller.js
Normal file
68
assets/controllers/pages/part_merge_modal_controller.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Controller} from "@hotwired/stimulus";
|
||||
|
||||
export default class extends Controller
|
||||
{
|
||||
static targets = ['link', 'mode', 'otherSelect'];
|
||||
static values = {
|
||||
targetId: Number,
|
||||
};
|
||||
|
||||
connect() {
|
||||
}
|
||||
|
||||
update() {
|
||||
const link = this.linkTarget;
|
||||
const other_select = this.otherSelectTarget;
|
||||
|
||||
//Extract the mode using the mode radio buttons (we filter the array to get the checked one)
|
||||
const mode = (this.modeTargets.filter((e)=>e.checked))[0].value;
|
||||
|
||||
if (other_select.value === '') {
|
||||
link.classList.add('disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
//Extract href template from data attribute on link target
|
||||
let href = link.getAttribute('data-href-template');
|
||||
|
||||
let target, other;
|
||||
if (mode === '1') {
|
||||
target = this.targetIdValue;
|
||||
other = other_select.value;
|
||||
} else if (mode === '2') {
|
||||
target = other_select.value;
|
||||
other = this.targetIdValue;
|
||||
} else {
|
||||
throw 'Invalid mode';
|
||||
}
|
||||
|
||||
//Replace placeholder with actual target id
|
||||
href = href.replace('__target__', target);
|
||||
//Replace placeholder with selected value of the select (the event sender)
|
||||
href = href.replace('__other__', other);
|
||||
|
||||
//Assign new href to link
|
||||
link.setAttribute('href', href);
|
||||
//Make link clickable
|
||||
link.classList.remove('disabled');
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
'use strict';
|
||||
|
||||
import {Dropdown} from "bootstrap";
|
||||
import ClipboardJS from "clipboard";
|
||||
|
||||
class RegisterEventHelper {
|
||||
constructor() {
|
||||
@@ -27,6 +28,11 @@ class RegisterEventHelper {
|
||||
this.configureDropdowns();
|
||||
this.registerSpecialCharInput();
|
||||
|
||||
//Initialize ClipboardJS
|
||||
this.registerLoadHandler(() => {
|
||||
new ClipboardJS('.btn');
|
||||
})
|
||||
|
||||
this.registerModalDropRemovalOnFormSubmit();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
import {Tab, Dropdown} from "bootstrap";
|
||||
import {Tab, Dropdown, Collapse} from "bootstrap";
|
||||
import tab from "bootstrap/js/src/tab";
|
||||
|
||||
/**
|
||||
@@ -54,6 +54,7 @@ class TabRememberHelper {
|
||||
const first_element = merged[0] ?? null;
|
||||
if(first_element) {
|
||||
this.revealElementOnTab(first_element);
|
||||
this.revealElementInCollapse(first_element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +63,20 @@ class TabRememberHelper {
|
||||
* @param event
|
||||
*/
|
||||
onInvalid(event) {
|
||||
this.revealElementInCollapse(event.target);
|
||||
this.revealElementOnTab(event.target);
|
||||
this.revealElementInDropdown(event.target);
|
||||
}
|
||||
|
||||
revealElementInCollapse(element) {
|
||||
let collapse = element.closest('.collapse');
|
||||
|
||||
if(collapse) {
|
||||
let bs_collapse = Collapse.getOrCreateInstance(collapse);
|
||||
bs_collapse.show();
|
||||
}
|
||||
}
|
||||
|
||||
revealElementInDropdown(element) {
|
||||
let dropdown = element.closest('.dropdown-menu');
|
||||
|
||||
|
||||
@@ -4,6 +4,13 @@
|
||||
use App\Kernel;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
|
||||
//Increase xdebug.max_nesting_level to 1000 if required (see issue #411)
|
||||
//Check if xdebug extension is active, and xdebug.max_nesting_level is set to 256 or lower
|
||||
if (extension_loaded('xdebug') && ((int) ini_get('xdebug.max_nesting_level')) <= 256) {
|
||||
//Increase xdebug.max_nesting_level to 1000
|
||||
ini_set('xdebug.max_nesting_level', '1000');
|
||||
}
|
||||
|
||||
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
|
||||
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@ coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 5%
|
||||
threshold: 10%
|
||||
target: 40%
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"ext-intl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"api-platform/core": "^3.1",
|
||||
"beberlei/doctrineextensions": "^1.2",
|
||||
"brick/math": "^0.11.0",
|
||||
"composer/package-versions-deprecated": "^1.11.99.5",
|
||||
@@ -33,6 +34,7 @@
|
||||
"liip/imagine-bundle": "^2.2",
|
||||
"nbgrp/onelogin-saml-bundle": "^1.3",
|
||||
"nelexa/zip": "^4.0",
|
||||
"nelmio/cors-bundle": "^2.3",
|
||||
"nelmio/security-bundle": "^3.0",
|
||||
"nyholm/psr7": "^1.1",
|
||||
"ocramius/proxy-manager": "2.2.*",
|
||||
@@ -40,6 +42,7 @@
|
||||
"part-db/label-fonts": "^1.0",
|
||||
"php-translation/symfony-bundle": "^0.14.0",
|
||||
"phpdocumentor/reflection-docblock": "^5.2",
|
||||
"phpstan/phpdoc-parser": "^1.23",
|
||||
"s9e/text-formatter": "^2.1",
|
||||
"scheb/2fa-backup-code": "^6.8.0",
|
||||
"scheb/2fa-bundle": "^6.8.0",
|
||||
@@ -67,6 +70,7 @@
|
||||
"symfony/runtime": "6.3.*",
|
||||
"symfony/security-bundle": "6.3.*",
|
||||
"symfony/serializer": "6.3.*",
|
||||
"symfony/string": "6.3.*",
|
||||
"symfony/translation": "6.3.*",
|
||||
"symfony/twig-bundle": "6.3.*",
|
||||
"symfony/ux-translator": "^2.10",
|
||||
@@ -94,6 +98,7 @@
|
||||
"phpstan/phpstan-doctrine": "^1.2.11",
|
||||
"phpstan/phpstan-strict-rules": "^1.5",
|
||||
"phpstan/phpstan-symfony": "^1.1.7",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"psalm/plugin-symfony": "^v5.0.1",
|
||||
"rector/rector": "^0.18.0",
|
||||
"roave/security-advisories": "dev-latest",
|
||||
|
||||
3654
composer.lock
generated
3654
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -31,4 +31,6 @@ return [
|
||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||
Jbtronics\DompdfFontLoaderBundle\DompdfFontLoaderBundle::class => ['all' => true],
|
||||
KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true],
|
||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
28
config/packages/api_platform.yaml
Normal file
28
config/packages/api_platform.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
api_platform:
|
||||
|
||||
title: 'Part-DB API'
|
||||
description: 'API of Part-DB'
|
||||
|
||||
version: '0.1.0'
|
||||
|
||||
# eager_loading:
|
||||
# max_joins: 100
|
||||
|
||||
keep_legacy_inflector: false
|
||||
|
||||
swagger:
|
||||
api_keys:
|
||||
# overridden in OpenApiFactoryDecorator
|
||||
access_token:
|
||||
name: Authorization
|
||||
type: header
|
||||
|
||||
defaults:
|
||||
# TODO: Change this to true later. In the moment it is false, because we use the session in somewhere
|
||||
stateless: false
|
||||
cache_headers:
|
||||
vary: ['Content-Type', 'Authorization', 'Origin']
|
||||
extra_properties:
|
||||
standard_put: true
|
||||
|
||||
pagination_client_items_per_page: true # Allow clients to override the default items per page
|
||||
@@ -5,6 +5,7 @@ parameters:
|
||||
saml.sp.privateKey: '%env(string:SAML_SP_PRIVATE_KEY)%'
|
||||
|
||||
nbgrp_onelogin_saml:
|
||||
use_proxy_vars: '%env(bool:SAML_BEHIND_PROXY)%'
|
||||
onelogin_settings:
|
||||
default:
|
||||
# Basic settings
|
||||
|
||||
10
config/packages/nelmio_cors.yaml
Normal file
10
config/packages/nelmio_cors.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
nelmio_cors:
|
||||
defaults:
|
||||
origin_regex: true
|
||||
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
|
||||
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
allow_headers: ['Content-Type', 'Authorization']
|
||||
expose_headers: ['Link']
|
||||
max_age: 3600
|
||||
paths:
|
||||
'^/': null
|
||||
@@ -24,6 +24,9 @@ security:
|
||||
# Enable user impersonation
|
||||
switch_user: { role: CAN_SWITCH_USER }
|
||||
|
||||
custom_authenticators:
|
||||
- App\Security\ApiTokenAuthenticator
|
||||
|
||||
two_factor:
|
||||
auth_form_path: 2fa_login
|
||||
check_path: 2fa_login_check
|
||||
@@ -66,3 +69,5 @@ security:
|
||||
# We get into trouble with the U2F authentication, if the calls to the trees trigger an 2FA login
|
||||
# This settings should not do much harm, because a read only access to show available data structures is not really critical
|
||||
- { path: "^/\\w{2}/tree", role: PUBLIC_ACCESS }
|
||||
# Restrict access to API to users, which has the API access permission
|
||||
- { path: "^/api", allow_if: 'is_granted("@api.access_api") and is_authenticated()' }
|
||||
|
||||
@@ -20,6 +20,7 @@ twig:
|
||||
avatar_helper: '@App\Services\UserSystem\UserAvatarHelper'
|
||||
available_themes: '%partdb.available_themes%'
|
||||
saml_enabled: '%partdb.saml.enabled%'
|
||||
part_preview_generator: '@App\Services\Attachments\PartPreviewGenerator'
|
||||
|
||||
when@test:
|
||||
twig:
|
||||
|
||||
@@ -53,7 +53,8 @@ parameters:
|
||||
######################################################################################################################
|
||||
# Table settings
|
||||
######################################################################################################################
|
||||
partdb.table.default_page_size: '%env(int:TABLE_DEFAULT_PAGE_SIZE)%' # The default number of entries shown per page in tables
|
||||
partdb.table.default_page_size: '%env(int:TABLE_DEFAULT_PAGE_SIZE)%' # The default number of entries shown per page in tables
|
||||
partdb.table.parts.default_columns: '%env(trim:string:TABLE_PARTS_DEFAULT_COLUMNS)%' # The default columns in part tables and their order
|
||||
|
||||
######################################################################################################################
|
||||
# Sidebar
|
||||
|
||||
@@ -25,27 +25,35 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
# If a part can be read by a user, he can also see all the datastructures (except devices)
|
||||
alsoSet: ['storelocations.read', 'footprints.read', 'categories.read', 'suppliers.read', 'manufacturers.read',
|
||||
'currencies.read', 'attachment_types.read', 'measurement_units.read']
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
edit:
|
||||
label: "perm.edit"
|
||||
alsoSet: ['read', 'parts_stock.withdraw', 'parts_stock.add', 'parts_stock.move']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
create:
|
||||
label: "perm.create"
|
||||
alsoSet: ['read', 'edit']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
delete:
|
||||
label: "perm.delete"
|
||||
alsoSet: ['read', 'edit']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
change_favorite:
|
||||
label: "perm.part.change_favorite"
|
||||
alsoSet: ['edit']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
show_history:
|
||||
label: "perm.part.show_history"
|
||||
alsoSet: ['read']
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
revert_element:
|
||||
label: "perm.revert_elements"
|
||||
alsoSet: ["read", "edit", "create", "delete", "show_history"]
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
import:
|
||||
label: "perm.import"
|
||||
alsoSet: ["read", "edit", "create"]
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
|
||||
parts_stock:
|
||||
group: "data"
|
||||
@@ -53,10 +61,13 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
operations:
|
||||
withdraw:
|
||||
label: "perm.parts_stock.withdraw"
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
add:
|
||||
label: "perm.parts_stock.add"
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
move:
|
||||
label: "perm.parts_stock.move"
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
|
||||
|
||||
storelocations: &PART_CONTAINING
|
||||
@@ -65,23 +76,30 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
operations:
|
||||
read:
|
||||
label: "perm.read"
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
edit:
|
||||
label: "perm.edit"
|
||||
alsoSet: 'read'
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
create:
|
||||
label: "perm.create"
|
||||
alsoSet: ['read', 'edit']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
delete:
|
||||
label: "perm.delete"
|
||||
alsoSet: ['read', 'edit']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
show_history:
|
||||
label: "perm.show_history"
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
revert_element:
|
||||
label: "perm.revert_elements"
|
||||
alsoSet: ["read", "edit", "create", "delete", "show_history"]
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
import:
|
||||
label: "perm.import"
|
||||
alsoSet: [ "read", "edit", "create" ]
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
|
||||
footprints:
|
||||
<<: *PART_CONTAINING
|
||||
@@ -145,6 +163,7 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
create_parts:
|
||||
label: "perm.part.info_providers.create_parts"
|
||||
alsoSet: ['parts.create']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
|
||||
groups:
|
||||
label: "perm.groups"
|
||||
@@ -152,26 +171,34 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
operations:
|
||||
read:
|
||||
label: "perm.read"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
edit:
|
||||
label: "perm.edit"
|
||||
alsoSet: 'read'
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
create:
|
||||
label: "perm.create"
|
||||
alsoSet: ['read', 'edit']
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
delete:
|
||||
label: "perm.delete"
|
||||
alsoSet: ['read', 'delete']
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
edit_permissions:
|
||||
label: "perm.edit_permissions"
|
||||
alsoSet: ['read', 'edit']
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
show_history:
|
||||
label: "perm.show_history"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
revert_element:
|
||||
label: "perm.revert_elements"
|
||||
alsoSet: ["read", "edit", "create", "delete", "edit_permissions", "show_history"]
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
import:
|
||||
label: "perm.import"
|
||||
alsoSet: [ "read", "edit", "create" ]
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
|
||||
users:
|
||||
label: "perm.users"
|
||||
@@ -179,37 +206,49 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
operations:
|
||||
read:
|
||||
label: "perm.read"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
create:
|
||||
label: "perm.create"
|
||||
alsoSet: ['read', 'edit_username', 'edit_infos']
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
delete:
|
||||
label: "perm.delete"
|
||||
alsoSet: ['read', 'edit_username', 'edit_infos']
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
edit_username:
|
||||
label: "perm.users.edit_user_name"
|
||||
alsoSet: ['read']
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
edit_infos:
|
||||
label: "perm.users.edit_infos"
|
||||
alsoSet: 'read'
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
edit_permissions:
|
||||
label: "perm.users.edit_permissions"
|
||||
alsoSet: 'read'
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
set_password:
|
||||
label: "perm.users.set_password"
|
||||
alsoSet: 'read'
|
||||
apiTokenRole: ROLE_API_FULL
|
||||
impersonate:
|
||||
label: "perm.users.impersonate"
|
||||
alsoSet: ['set_password']
|
||||
apiTokenRole: ROLE_API_FULL
|
||||
change_user_settings:
|
||||
label: "perm.users.change_user_settings"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
show_history:
|
||||
label: "perm.show_history"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
revert_element:
|
||||
label: "perm.revert_elements"
|
||||
alsoSet: ["read", "create", "delete", "edit_permissions", "show_history", "edit_infos", "edit_username"]
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
import:
|
||||
label: "perm.import"
|
||||
alsoSet: [ "read", "create" ]
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
|
||||
#database:
|
||||
# label: "perm.database"
|
||||
@@ -244,64 +283,94 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
|
||||
operations:
|
||||
show_logs:
|
||||
label: "perm.show_logs"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
delete_logs:
|
||||
label: "perm.delete_logs"
|
||||
alsoSet: 'show_logs'
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
server_infos:
|
||||
label: "perm.server_infos"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
manage_oauth_tokens:
|
||||
label: "Manage OAuth tokens"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
show_updates:
|
||||
label: "perm.system.show_available_updates"
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
|
||||
|
||||
attachments:
|
||||
label: "perm.part.attachments"
|
||||
operations:
|
||||
show_private:
|
||||
label: "perm.attachments.show_private"
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
list_attachments:
|
||||
label: "perm.attachments.list_attachments"
|
||||
alsoSet: ['attachment_types.read']
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
|
||||
self:
|
||||
label: "perm.self"
|
||||
operations:
|
||||
edit_infos:
|
||||
label: "perm.self.edit_infos"
|
||||
apiTokenRole: ROLE_API_FULL
|
||||
edit_username:
|
||||
label: "perm.self.edit_username"
|
||||
apiTokenRole: ROLE_API_FULL
|
||||
show_permissions:
|
||||
label: "perm.self.show_permissions"
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
show_logs:
|
||||
label: "perm.self.show_logs"
|
||||
apiTokenRole: ROLE_API_FULL
|
||||
|
||||
labels:
|
||||
label: "perm.labels"
|
||||
operations:
|
||||
create_labels:
|
||||
label: "perm.self.create_labels"
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
edit_options:
|
||||
label: "perm.self.edit_options"
|
||||
alsoSet: ['create_labels']
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
read_profiles:
|
||||
label: "perm.self.read_profiles"
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
edit_profiles:
|
||||
label: "perm.self.edit_profiles"
|
||||
alsoSet: ['read_profiles']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
create_profiles:
|
||||
label: "perm.self.create_profiles"
|
||||
alsoSet: ['read_profiles', 'edit_profiles']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
delete_profiles:
|
||||
label: "perm.self.delete_profiles"
|
||||
alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
use_twig:
|
||||
label: "perm.labels.use_twig"
|
||||
alsoSet: ['create_labels', 'edit_options']
|
||||
apiTokenRole: ROLE_API_ADMIN
|
||||
show_history:
|
||||
label: "perm.show_history"
|
||||
alsoSet: ['read_profiles']
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
revert_element:
|
||||
label: "perm.revert_elements"
|
||||
alsoSet: ['read_profiles', 'edit_profiles', 'create_profiles', 'delete_profiles']
|
||||
apiTokenRole: ROLE_API_EDIT
|
||||
|
||||
|
||||
api:
|
||||
label: "perm.api"
|
||||
operations:
|
||||
access_api:
|
||||
label: "perm.api.access_api"
|
||||
apiTokenRole: ROLE_API_READ_ONLY
|
||||
manage_tokens:
|
||||
label: "perm.api.manage_tokens"
|
||||
alsoSet: ['access_api']
|
||||
apiTokenRole: ROLE_API_FULL
|
||||
@@ -15,5 +15,5 @@ redirector:
|
||||
requirements:
|
||||
url: ".*"
|
||||
controller: App\Controller\RedirectController::addLocalePart
|
||||
# Dont match localized routes (no redirection loop, if no root with that name exists)
|
||||
condition: "not (request.getPathInfo() matches '/^\\\\/[a-z]{2}(_[A-Z]{2})?\\\\//')"
|
||||
# Dont match localized routes (no redirection loop, if no root with that name exists) or API prefixed routes
|
||||
condition: "not (request.getPathInfo() matches '/^\\\\/([a-z]{2}(_[A-Z]{2})?|api)\\\\//')"
|
||||
4
config/routes/api_platform.yaml
Normal file
4
config/routes/api_platform.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
api_platform:
|
||||
resource: .
|
||||
type: api_platform
|
||||
prefix: /api
|
||||
@@ -211,6 +211,15 @@ services:
|
||||
arguments:
|
||||
$saml_enabled: '%partdb.saml.enabled%'
|
||||
|
||||
####################################################################################################################
|
||||
# Table settings
|
||||
####################################################################################################################
|
||||
App\DataTables\PartsDataTable:
|
||||
arguments:
|
||||
$visible_columns: '%partdb.table.parts.default_columns%'
|
||||
|
||||
App\DataTables\Helpers\ColumnSortHelper:
|
||||
shared: false # Service has a state so not share it between different tables
|
||||
|
||||
####################################################################################################################
|
||||
# Label system
|
||||
@@ -277,6 +286,22 @@ services:
|
||||
$search_limit: '%env(int:PROVIDER_OCTOPART_SEARCH_LIMIT)%'
|
||||
$onlyAuthorizedSellers: '%env(bool:PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS)%'
|
||||
|
||||
App\Services\InfoProviderSystem\Providers\MouserProvider:
|
||||
arguments:
|
||||
$api_key: '%env(string:PROVIDER_MOUSER_KEY)%'
|
||||
$language: '%env(string:PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE)%'
|
||||
$options: '%env(string:PROVIDER_MOUSER_SEARCH_OPTION)%'
|
||||
$search_limit: '%env(int:PROVIDER_MOUSER_SEARCH_LIMIT)%'
|
||||
|
||||
####################################################################################################################
|
||||
# API system
|
||||
####################################################################################################################
|
||||
App\State\PartDBInfoProvider:
|
||||
arguments:
|
||||
$default_uri: '%partdb.default_uri%'
|
||||
$global_locale: '%partdb.locale%'
|
||||
$global_timezone: '%partdb.timezone%'
|
||||
|
||||
####################################################################################################################
|
||||
# Symfony overrides
|
||||
####################################################################################################################
|
||||
@@ -319,6 +344,12 @@ services:
|
||||
arguments:
|
||||
$check_for_updates: '%partdb.check_for_updates%'
|
||||
|
||||
App\Services\System\BannerHelper:
|
||||
arguments:
|
||||
$partdb_banner: '%partdb.banner%'
|
||||
$project_dir: '%kernel.project_dir%'
|
||||
|
||||
|
||||
####################################################################################################################
|
||||
# Monolog
|
||||
####################################################################################################################
|
||||
|
||||
78
docs/api/authentication.md
Normal file
78
docs/api/authentication.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Authentication
|
||||
layout: default
|
||||
parent: API
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# Authentication
|
||||
|
||||
To use API endpoints, the external application has to authenticate itself, so that Part-DB knows which user is accessing
|
||||
the data and which permissions
|
||||
the application should have during the access. Authentication is always bound to a specific user, so the external
|
||||
applications is acting on behalf of a
|
||||
specific user. This user limits the permissions of the application, so that it can only access data, which the user is
|
||||
allowed to access.
|
||||
|
||||
The only method currently available for authentication is to use API tokens:
|
||||
|
||||
## API tokens
|
||||
|
||||
An API token is a long alphanumeric string, which is bound to a specific user and can be used to authenticate as this
|
||||
user, when accessing the API.
|
||||
The API token is passed via the `Authentication` HTTP header during the API request, like the
|
||||
following: `Authentication: Bearer tcp_sdjfks....`.
|
||||
|
||||
{: .important }
|
||||
> Everybody who knows the API token can access the API as the user, which is bound to the token. So you should treat the
|
||||
> API token like a password
|
||||
> and keep it secret. Only share it with trusted applications.
|
||||
|
||||
API tokens can be created and managed on the user settings page in the API token section. You can create as many API
|
||||
tokens as you want and also delete them again.
|
||||
When deleting a token, it is immediately invalidated and can not be used anymore, which means that the application can
|
||||
not access the API anymore with this token.
|
||||
|
||||
### Token permissions and scopes
|
||||
|
||||
API tokens are ultimately limited by the permissions of the user, which belongs to the token. That means that the token
|
||||
can only access data, which the user is allowed to access, no matter the token permissions.
|
||||
|
||||
But you can further limit the permissions of a token by choosing a specific scope for the token. The scope defines which
|
||||
subset of permissions the token has, which can be less than the permissions of the user. For example, you can have a
|
||||
user
|
||||
with full read and write permissions, but create a token with only read permissions, which can only read data, but not
|
||||
change anything in the database.
|
||||
|
||||
{: .warning }
|
||||
> In general, you should always use the least possible permissions for a token, to limit the possible damage, which can
|
||||
> be done with a stolen token or a bug in the application.
|
||||
> Only use the full or admin scope, if you really need it, as they could potentially be used to do a lot of damage to
|
||||
> your Part-DB instance.
|
||||
|
||||
Following token scopes are available:
|
||||
|
||||
* **Read-Only**: The token can only read non-sensitive data (like parts, but no users or groups) from the API and can
|
||||
not change anything.
|
||||
* **Edit**: The token can read and write non-sensitive data via the API. This includes creating, updating and deleting
|
||||
data. This should be enough for most applications.
|
||||
* **Admin**: The token can read and write all data via the API, including sensitive data like users and groups. This
|
||||
should only be used for trusted applications, which need to access sensitive data, and perform administrative actions.
|
||||
* **Full**: The token can do anything the user can do, including changing the users password and create new tokens. This
|
||||
should only be used for highly trusted applications!!
|
||||
|
||||
Please note, that in early versions of the API, there might be no endpoints yet, to really perform the actions, which
|
||||
would be allowed by the token scope.
|
||||
|
||||
### Expiration date
|
||||
|
||||
API tokens can have an expiration date, which means that the token is only valid until the expiration date. After that
|
||||
the token is automatically invalidated and can not be used anymore. The token is still listed on the user settings page,
|
||||
and can be deleted there, but the code can not be used to access Part-DB anymore after the expiration date.
|
||||
|
||||
### Get token information
|
||||
|
||||
When authenticating with an API token, you can get information about the currently used token by accessing
|
||||
the `/api/tokens/current` endpoint.
|
||||
It gives you information about the token scope, expiration date and the user, which is bound to the token and the last
|
||||
time the token was used.
|
||||
11
docs/api/index.md
Normal file
11
docs/api/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
layout: default
|
||||
title: API
|
||||
nav_order: 7
|
||||
has_children: true
|
||||
---
|
||||
|
||||
# API
|
||||
|
||||
Part-DB provides a REST API to access the data stored in the database.
|
||||
In this section you can find information about the API and how to use it.
|
||||
178
docs/api/intro.md
Normal file
178
docs/api/intro.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
title: Introduction
|
||||
layout: default
|
||||
parent: API
|
||||
nav_order: 1
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
Part-DB provides a [REST API](https://en.wikipedia.org/wiki/REST) to programmatically access the data stored in the
|
||||
database.
|
||||
This allows external applications to interact with Part-DB, extend it or integrate it into other applications.
|
||||
|
||||
{: .warning }
|
||||
> This feature is currently in beta. Please report any bugs you find.
|
||||
> The API should not be considered stable yet and could change in future versions, without prior notice.
|
||||
> Some features might be missing or not working yet.
|
||||
> Also be aware, that there might be security issues in the API, which could allow attackers to access or edit data via
|
||||
> the API, which
|
||||
> they normally should be able to access. So currently you should only use the API with trusted users and trusted
|
||||
> applications.
|
||||
|
||||
Part-DB uses [API Platform](https://api-platform.com/) to provide the API, which allows for easy creation of REST APIs
|
||||
with Symfony and gives you a lot of features out of the box.
|
||||
See the [API Platform documentation](https://api-platform.com/docs/core/) for more details about the API Platform
|
||||
features and how to use them.
|
||||
|
||||
## Enable the API
|
||||
|
||||
The API is available under the `/api` path, but not reachable without proper permissions.
|
||||
You have to give the users, which should be able to access the API the proper permissions (Miscellaneous -> API).
|
||||
Please note that there are two relevant permissions, the first one allows users to access the `/api/` path at all and
|
||||
showing the documentation,
|
||||
and the second one allows them to create API tokens which is needed for authentication of external applications.
|
||||
|
||||
## Authentication
|
||||
|
||||
To use API endpoints, the external application has to authenticate itself, so that Part-DB knows which user is accessing
|
||||
the data and
|
||||
which permissions the application should have. Basically this is done by creating an API token for a user and then
|
||||
passing it on every request
|
||||
with the `Authorization` header as bearer token, so you add a header `Authorization: Bearer <your token>`.
|
||||
|
||||
See [Authentication chapter]({% link api/authentication.md %}) for more details.
|
||||
|
||||
## API endpoints
|
||||
|
||||
The API is split into different endpoints, which are reachable under the `/api/` path of your Part-DB instance (
|
||||
so `https://your-part-db.local/api/`).
|
||||
There are various endpoints for each entity type (like `part`, `manufacturer`, etc.), which allow you to read and write
|
||||
data and some special endpoints like `search` or `statistics`.
|
||||
|
||||
For example all API endpoints for managing categories are available under `/api/categories/`. Depending on the exact
|
||||
path and the HTTP method used, you can read, create, update or delete categories.
|
||||
For most entities, there are endpoints like this:
|
||||
|
||||
* **GET**: `/api/categories/` - List all categories in the database (with pagination of the results)
|
||||
* **POST**: `/api/categories/` - Create a new category
|
||||
* **GET**: `/api/categories/{id}` - Get a specific category by its ID
|
||||
* **DELETE**: `/api/categories/{id}` - Delete a specific category by its ID
|
||||
* **UPDATE**: `/api/categories/{id}` - Update a specific category by its ID. Only the fields which are sent in the
|
||||
request are updated, all other fields are left unchanged.
|
||||
Be aware that you have to set the [JSON Merge Patch](https://datatracker.ietf.org/doc/html/rfc7386) content type
|
||||
header (`Content-Type: application/merge-patch+json`) for this to work.
|
||||
|
||||
A full (interactive) list of endpoints can be displayed when visiting the `/api/` path in your browser, when you are
|
||||
logged in with a user, which is allowed to access the API.
|
||||
There is also a link to this page, on the user settings page in the API token section.
|
||||
This documentation also list all available fields for each entity type and the allowed operations.
|
||||
|
||||
## Formats
|
||||
|
||||
The API supports different formats for the request and response data, which you can control via the `Accept`
|
||||
and `Content-Type` headers.
|
||||
You should use [JSON-LD](https://json-ld.org/) as format, which is basically JSON with some additional metadata, which
|
||||
allows
|
||||
to describe the data in a more structured way and also allows to link between different entities. You can achieve this
|
||||
by setting `Accept: application/ld+json` header to the API requests.
|
||||
|
||||
To get plain JSON without any metadata or links, use the `Accept: application/json` header.
|
||||
|
||||
Without an `Accept` header (e.g. when you call the endpoint in a browser), the API will return an HTML page with the
|
||||
documentation, so be sure to include the desired `Accept` header in your API requests.
|
||||
If you can not control the `Accept` header, you can add an `.json` or `.jsonld` suffix to the URL to enforce a JSON or
|
||||
JSON-LD response (e.g. `/api/parts.jsonld`).
|
||||
|
||||
## OpenAPI schema
|
||||
|
||||
Part-DB provides a [OpenAPI](https://swagger.io/specification/) (formally Swagger) schema for the API
|
||||
under `/api/docs.json` (so `https://your-part-db.local/api/docs.json`).
|
||||
This schema is a machine-readable description of the API, which can be imported in software to test the API or even
|
||||
automatically generate client libraries for the API.
|
||||
|
||||
API generators which can generate a client library for the API from the schema are available for many programming
|
||||
languages, like [OpenAPI Generator](https://openapi-generator.tech/).
|
||||
|
||||
An JSONLD/Hydra version of the schema is also available under `/api/docs.jsonld` (
|
||||
so `https://your-part-db.local/api/docs.jsonld`).
|
||||
|
||||
## Interactive documentation
|
||||
|
||||
Part-DB provides an interactive documentation for the API, which is available under `/api/docs` (
|
||||
so `https://your-part-db.local/api/docs`).
|
||||
You can pass your API token in the form on the top of the page, to authenticate yourself, and then you can try out the
|
||||
API directly in the browser.
|
||||
This is a great way to test the API and see how it works, without having to write any code.
|
||||
|
||||
## Pagination
|
||||
|
||||
By default, all list endpoints are paginated, which means only a certain number of results is returned per request.
|
||||
To get another page of the results, you have to use the `page` query parameter, which contains the page number you want
|
||||
to get (e.g. `/api/categoues/?page=2`).
|
||||
When using JSONLD, the links to the next page are also included in the `hydra:view` property of the response.
|
||||
|
||||
To change the size of the pages (the number of items in a single page) use the `itemsPerPage` query parameter (
|
||||
e.g. `/api/categoues/?itemsPerPage=50`).
|
||||
|
||||
See [API Platform docs](https://api-platform.com/docs/core/pagination) for more infos.
|
||||
|
||||
## Filtering results / Searching
|
||||
|
||||
When retrieving a list of entities, you can restrict the results by various filters. Almost all entities have a search
|
||||
filter,
|
||||
which allows you to only include entities, which (text) fields match the given search term: For example if you only want
|
||||
to
|
||||
get parts, with the Name "BC547", you can use `/api/parts.jsonld?name=BC547`. You can use `%` as wildcard for multiple
|
||||
characters
|
||||
in the search term (Be sure to properly encode the search term, if you use special characters). For example if you want
|
||||
to get all parts,
|
||||
whose name starts with "BC", you can use `/api/parts.jsonld?name=BC%25` (the `%25` is the url encoded version of `%`).
|
||||
|
||||
There are other filters available for some entities, allowing you to search on other fields, or restricting the results
|
||||
by numeric values or dates. See the endpoint documentation for the available filters.
|
||||
|
||||
## Filter by associated entities
|
||||
|
||||
To get all parts with a certain category, manufacturer, etc. you can use the `category`, `manufacturer`, etc. query
|
||||
parameters of the `/api/parts` endpoint.
|
||||
They are so-called entity filters and accept a comma separated list of IDs of the entities you want to filter by.
|
||||
For example if you want to get all parts with the category "Resistor" (Category ID 1) and "Capacitor" (Category ID 2),
|
||||
you can use `/api/parts.jsonld?category=1,2`.
|
||||
|
||||
Suffix an id with `+` to suffix, to include all direct children categories of the given category. Use the `++` suffix to
|
||||
include all children categories recursively.
|
||||
To get all parts with the category "Resistor" (Category ID 1) and all children categories of "Capacitor" (Category ID
|
||||
2), you can use `/api/parts.jsonld?category=1,2++`.
|
||||
|
||||
See the endpoint documentation for the available entity filters.
|
||||
|
||||
## Ordering results
|
||||
|
||||
When retrieving a list of entities, you can order the results by various fields using the `order` query parameter.
|
||||
For example if you want to get all parts ordered by their name, you can use `/api/parts/?order[name]=asc`. You can use
|
||||
this parameter multiple times to order by multiple fields.
|
||||
|
||||
See the endpoint documentation for the available fields to order by.
|
||||
|
||||
## Property filter
|
||||
|
||||
Sometimes you only want to get a subset of the properties of an entity, for example when you only need the name of a
|
||||
part, but not all the other properties.
|
||||
You can achieve this using the `properties[]` query parameter with the name of the field you want to get. You can use
|
||||
this parameter multiple times to get multiple fields.
|
||||
For example if you only want to get the name and the description of a part, you can
|
||||
use `/api/parts/123?properties[]=name&properties[]=description`.
|
||||
It is also possible to use this filters on list endpoints (get collection), to only get a subset of the properties of
|
||||
all entities in the collection.
|
||||
|
||||
See [API Platform docs](https://api-platform.com/docs/core/filters/#property-filter) for more infos.
|
||||
|
||||
## Change comment
|
||||
|
||||
Similar to the changes using Part-DB web interface, you can add a change comment to every change you make via the API,
|
||||
which will be
|
||||
visible in the log of the entity.
|
||||
|
||||
You can pass the text for this via the `_comment` query parameter (beware the proper encoding). For
|
||||
example `/api/parts/123?_comment=This%20is%20a%20change%20comment`.
|
||||
211
docs/concepts.md
211
docs/concepts.md
@@ -5,6 +5,7 @@ nav_order: 2
|
||||
---
|
||||
|
||||
# Concepts
|
||||
|
||||
This page explains the different concepts of Part-DB and what their intended use is:
|
||||
|
||||
1. TOC
|
||||
@@ -13,42 +14,76 @@ This page explains the different concepts of Part-DB and what their intended use
|
||||
## Part managment
|
||||
|
||||
### Part
|
||||
A part is the central concept of Part-DB. A part represents a single kind (or type) of a thing, like an electronic component, an device, an book or similar (depending on what you use Part-DB for). A part entity just represents a certain type of a thing, so if you have 1000 times an BC547 transistor you would create ONE part with the name BC547 and set its quantity to 1000. The individual quantities (so a single BC547 transistor) of a part, should be indistinguishable from each other, so that it does not matter which one of your 1000 things of Part you use.
|
||||
|
||||
A part is the central concept of Part-DB. A part represents a single kind (or type) of a thing, like an electronic
|
||||
component, a device, a book or similar (depending on what you use Part-DB for). A part entity just represents a certain
|
||||
type of thing, so if you have 1000 times an BC547 transistor you would create ONE part with the name BC547 and set its
|
||||
quantity to 1000. The individual quantities (so a single BC547 transistor) of a part, should be indistinguishable from
|
||||
each other, so that it does not matter which one of your 1000 things of Part you use.
|
||||
A part entity have many fields, which can be used to describe it better. Most of the fields are optional:
|
||||
* **Name** (Required): The name of the part or how you wanna call it. This could be an manufacturer provided name, or a name you thought of your self. The name have to be unique in a single category.
|
||||
* **Description**: A short (single-line) description of what this part is/does. For longer informations you should use the comment field or the specifications
|
||||
|
||||
* **Name** (Required): The name of the part or how you want to call it. This could be a manufacturer provided name, or a
|
||||
name you thought of your self. The name have to be unique in a single category.
|
||||
* **Description**: A short (single-line) description of what this part is/does. For longer information you should use
|
||||
the comment field or the specifications
|
||||
* **Category** (Required): The category (see there) to which this part belongs to.
|
||||
* **Tags**: The list of tags this part belong to. Tags can be used to group parts logically (similar to the category), but tags are much less strict and formal (they dont have to be defined forehands) and you can assign multiple tags to a part. When clicking on a tag, a list with all parts which have the same tag, is shown.
|
||||
* **Min Instock**: *Not really implemented yet*. Parts where the total instock is below this value, will show up for ordering.
|
||||
* **Footprint**: See there. Useful especially for electronic parts, which have one of the common electronic footprints (like DIP8, SMD0805 or similar). If a part has no explicit defined preview picture, the preview picture of its footprint will be shown instead in tables.
|
||||
* **Tags**: The list of tags this part belong to. Tags can be used to group parts logically (similar to the category),
|
||||
but tags are much less strict and formal (they don't have to be defined forehands) and you can assign multiple tags to
|
||||
a part. When clicking on a tag, a list with all parts which have the same tag, is shown.
|
||||
* **Min Instock**: *Not really implemented yet*. Parts where the total instock is below this value, will show up for
|
||||
ordering.
|
||||
* **Footprint**: See there. Useful especially for electronic parts, which have one of the common electronic footprints (
|
||||
like DIP8, SMD0805 or similar). If a part has no explicit defined preview picture, the preview picture of its
|
||||
footprint will be shown instead in tables.
|
||||
* **Manufacturer**: The manufacturer which has manufactured (not sold) this part. See Manufacturer entity for more info.
|
||||
* **Manufacturer part number** (MPN): If you have used your own name for a part, you can put the part number the manufacturer uses in this field, so that you can find a part also under its manufacturer number.
|
||||
* **Link to product page**: If you want to link to the manufacturer website of a part, and it is not possible to determine it automatically from the part name, set in the manufacturer entity (or no manfacturer is set), you can set the link here for each part individually.
|
||||
* **Manufacturing Status**: The manufacturing status of this part, meaning the information about where the part is in its manufacturing lifecycle.
|
||||
* **Needs review**: If you think parts informations maybe are inaccurate or incomplete and needs some later review/checking, you can set this flag. A part with this flag is marked, so that users know the informations are not completly trustworthy.
|
||||
* **Manufacturer part number** (MPN): If you have used your own name for a part, you can put the part number the
|
||||
manufacturer uses in this field, so that you can find a part also under its manufacturer number.
|
||||
* **Link to product page**: If you want to link to the manufacturer website of a part, and it is not possible to
|
||||
determine it automatically from the part name, set in the manufacturer entity (or no manufacturer is set), you can set
|
||||
the link here for each part individually.
|
||||
* **Manufacturing Status**: The manufacturing status of this part, meaning the information about where the part is in
|
||||
its manufacturing lifecycle.
|
||||
* **Needs review**: If you think parts information maybe are inaccurate or incomplete and needs some later
|
||||
review/checking, you can set this flag. A part with this flag is marked, so that users know the information are not
|
||||
completely trustworthy.
|
||||
* **Favorite**: Parts with this flag are highlighted in parts lists
|
||||
* **Mass**: The mass of a single piece of this part (so of a single transistor). Given in grams.
|
||||
* **Internal Part number** (IPN): Each part is automatically assigned an numerical ID which identifies a part in the database. This ID depends on when a part was created and can not be changed. If you want to assign your own unique identifiers, or sync parts identifiers with the identifiers of another database you can use this field.
|
||||
* **Internal Part number** (IPN): Each part is automatically assigned a numerical ID which identifies a part in the
|
||||
database. This ID depends on when a part was created and can not be changed. If you want to assign your own unique
|
||||
identifiers, or sync parts identifiers with the identifiers of another database you can use this field.
|
||||
|
||||
### Stock / Part lot
|
||||
A part can have many stock at multiple different locations. This is represented by part lots / stocks, which consists basically of a storelocation (so where are the parts of this lot are stored) and an amount (how many parts are there).
|
||||
|
||||
### Purchase Informations
|
||||
The purchase informations describe where the part can be bought (at which vendors) and to which prices.
|
||||
The first part (the order information) describes at which supplier the part can be bought and which is the name of the part under which you can order the part there.
|
||||
An order information can contain multiple price informations, which describes the prices for the part at the supplier including bulk discount, etc.
|
||||
A part can have many stock at multiple different locations. This is represented by part lots / stocks, which consists
|
||||
basically of a storage location (so where are the parts of this lot are stored) and an amount (how many parts are there).
|
||||
|
||||
### Purchase Information
|
||||
|
||||
The purchase information describe where the part can be bought (at which vendors) and to which prices.
|
||||
The first part (the order information) describes at which supplier the part can be bought and which is the name of the
|
||||
part under which you can order the part there.
|
||||
An order information can contain multiple price information, which describes the prices for the part at the supplier
|
||||
including bulk discount, etc.
|
||||
|
||||
### Parameters
|
||||
Parameters represents various specifications / parameters of a part, like the the maximum current of a diode, etc. The advantage of using parameters instead of just putting the data in the comment field or so, is that you can filter for parameters values (including ranges and more) later on.
|
||||
Parameters describe can describe numeric values and/or text values for which they can be filtered. This basically allows you to define custom fields on a part.
|
||||
|
||||
Using the group field a parameter allows you to group parameters together in the info page later (all parameters with the same group value will be shown under the same group title).
|
||||
Parameters represents various specifications / parameters of a part, like the maximum current of a diode, etc. The
|
||||
advantage of using parameters instead of just putting the data in the comment field or so, is that you can filter for
|
||||
parameters values (including ranges and more) later on.
|
||||
Parameters describe can describe numeric values and/or text values for which they can be filtered. This basically allows
|
||||
you to define custom fields on a part.
|
||||
|
||||
Using the group field a parameter allows you to group parameters together in the info page later (all parameters with
|
||||
the same group value will be shown under the same group title).
|
||||
|
||||
## Core data
|
||||
|
||||
### Category
|
||||
|
||||
A category is used to group parts logically by their function (e.g. all NPN transistors would be put in a "NPN-Transistors" category).
|
||||
Categories are hierarchical structures meaning that you can create logical trees to group categories together. A possible category tree could look like this:
|
||||
A category is used to group parts logically by their function (e.g. all NPN transistors would be put in a "
|
||||
NPN-Transistors" category).
|
||||
Categories are hierarchical structures meaning that you can create logical trees to group categories together. A
|
||||
possible category tree could look like this:
|
||||
|
||||
* Active Components
|
||||
* Transistors
|
||||
@@ -60,97 +95,145 @@ Categories are hierarchical structures meaning that you can create logical trees
|
||||
* MCUs
|
||||
* Passive Components
|
||||
* Capacitors
|
||||
* Resitors
|
||||
* Resistors
|
||||
|
||||
### Supplier
|
||||
A Supplier is a vendor / distributor where you can buy/order parts. Price informations of parts are associated with a supplier.
|
||||
|
||||
A Supplier is a vendor / distributor where you can buy/order parts. Price information of parts are associated with a
|
||||
supplier.
|
||||
|
||||
### Manufacturer
|
||||
A manufacturer represents the company that manufacturer / build various parts (not necessary sell them). If the manufacturer also sell the parts, you have to create a supplier for that.
|
||||
|
||||
### Storelocation
|
||||
A storelocation represents a place where parts can be stored. This could be a box, a shelf or other things (like the SMD feeder of a machine or so).
|
||||
A manufacturer represents the company that manufacturer / build various parts (not necessary sell them). If the
|
||||
manufacturer also sell the parts, you have to create a supplier for that.
|
||||
|
||||
Storelocations are hierarchical to represent storelocations contained in each other.
|
||||
### Storage location
|
||||
|
||||
A storage location represents a place where parts can be stored. This could be a box, a shelf or other things (like the
|
||||
SMD feeder of a machine or so).
|
||||
|
||||
Storage locations are hierarchical to represent storage locations contained in each other.
|
||||
An example tree could look like this:
|
||||
|
||||
* Shelf 1
|
||||
* Box 1
|
||||
* Box 2
|
||||
* Box shelf A1
|
||||
* Box shelf A2
|
||||
* Box shelf B1
|
||||
* Box shelf B2
|
||||
* Box 1
|
||||
* Box 2
|
||||
* Box shelf A1
|
||||
* Box shelf A2
|
||||
* Box shelf B1
|
||||
* Box shelf B2
|
||||
* Shelf 2
|
||||
* Cupboard
|
||||
|
||||
Storelocations should be defined down to the smallest possible location, to make finding the part again easy.
|
||||
Storage locations should be defined down to the smallest possible location, to make finding the part again easy.
|
||||
|
||||
### Footprint
|
||||
In electronics many components have one of the common components cases / footprints. The footprint entity describes such common footprints, which can be assigned to parts.
|
||||
You can assign an image (and an 3D model) as an attachment to a footprint, which will be used as preview for parts with this footprint, even if the parts do not have an explicitly assigned preview image.
|
||||
|
||||
Footprints are a hierachically which allows you to build logical sorted trees. An example tree could look like this:
|
||||
In electronics many components have one of the common components cases / footprints. The footprint entity describes such
|
||||
common footprints, which can be assigned to parts.
|
||||
You can assign an image (and an 3D model) as an attachment to a footprint, which will be used as preview for parts with
|
||||
this footprint, even if the parts do not have an explicitly assigned preview image.
|
||||
|
||||
Footprints are a hierarchically which allows you to build logical sorted trees. An example tree could look like this:
|
||||
|
||||
* Through-Hole components
|
||||
* DIP
|
||||
* DIP-8
|
||||
* DIP-28
|
||||
* DIP-28W
|
||||
* DIP-8
|
||||
* DIP-28
|
||||
* DIP-28W
|
||||
* TO
|
||||
* TO-92
|
||||
* TO-92
|
||||
* SMD components
|
||||
* SOIC
|
||||
* SO-8
|
||||
* SO-8
|
||||
* Resistors
|
||||
* 0805
|
||||
* 0603
|
||||
* 0805
|
||||
* 0603
|
||||
|
||||
### Measurement Unit
|
||||
By default part instock is counted in number of individual parts, which is fine for things like electronic components, which exists only in integer quantities. However if you have things with fractional units like the length of a wire or the volume of a liquid, you have to define a measurement unit.
|
||||
|
||||
By default, part instock is counted in number of individual parts, which is fine for things like electronic components,
|
||||
which exists only in integer quantities. However, if you have things with fractional units like the length of a wire or
|
||||
the volume of a liquid, you have to define a measurement unit.
|
||||
The measurement unit represents a physical quantity like mass, volume or length.
|
||||
|
||||
You can define a short unit for it (like m for Meters, or g for gramms) which will be shown, when a quantity of a part with this unit is shown.
|
||||
You can define a short unit for it (like m for Meters, or g for gramms) which will be shown, when a quantity of a part
|
||||
with this unit is shown.
|
||||
|
||||
### Currency
|
||||
By default all prices are set in the base currency configured for the instance (by default euros). If you want to use multiple currencies together (as e.g. vendors use foreign currencies for their price and you do not want to update the prices for every exchange rate change), you have to define these currencies here.
|
||||
|
||||
You can set an exchange rate here in terms of the base currency (or fetch it from the internet if configured). The exchange rate will be used to show users the prices in their preferred currency.
|
||||
By default, all prices are set in the base currency configured for the instance (by default euros). If you want to use
|
||||
multiple currencies together (as e.g. vendors use foreign currencies for their price, and you do not want to update the
|
||||
prices for every exchange rate change), you have to define these currencies here.
|
||||
|
||||
You can set an exchange rate here in terms of the base currency (or fetch it from the internet if configured). The
|
||||
exchange rate will be used to show users the prices in their preferred currency.
|
||||
|
||||
## Attachments
|
||||
|
||||
### Attachment
|
||||
An attachment is an file that can be associated with another entity (like a Part, Storelocation, User, etc.). This could for example be a datasheet in a Part, the logo of a vendor or some CAD drawing of a footprint.
|
||||
|
||||
An attachment has an attachment type (see below), which groups the attachments logically (and optionally restricts the allowed file types), a name describing the attachment and a file. The file can either be uploaded to the server and stored there, or given as a link to a file on another webpath. If configured in the settings, it is also possible that the webserver downloads the file from the supplied website and stores it locally on the server.
|
||||
An attachment is a file that can be associated with another entity (like a Part, Storelocation, User, etc.). This could
|
||||
for example be a datasheet in a Part, the logo of a vendor or some CAD drawing of a footprint.
|
||||
|
||||
By default all uploaded files, are accessible for everyone (even non logged in users), if the link is known. If your Part-DB instance is publicly available and you want to store private/sensitve files on it, you should mark the attachment as "Private attachment". Private attachments are only accessible to users, which has the permission to access private attachments.
|
||||
Please not, that no thumbnails are generated for private attachments, which can have an performance impact.
|
||||
An attachment has an attachment type (see below), which groups the attachments logically (and optionally restricts the
|
||||
allowed file types), a name describing the attachment and a file. The file can either be uploaded to the server and
|
||||
stored there, or given as a link to a file on another web path. If configured in the settings, it is also possible that
|
||||
the webserver downloads the file from the supplied website and stores it locally on the server.
|
||||
|
||||
Part-DB ships some preview images for various common footprints like DIP-8 and others, as internal ressources. These can be accessed/searched by typing the keyword in the URL field of a part and choosing one of the choices from the dropdown.
|
||||
By default, all uploaded files, are accessible for everyone (even non-logged-in users), if the link is known. If your
|
||||
Part-DB instance is publicly available, and you want to store private/sensitive files on it, you should mark the
|
||||
attachment as "Private attachment". Private attachments are only accessible to users, which has the permission to access
|
||||
private attachments.
|
||||
Please not, that no thumbnails are generated for private attachments, which can have a performance impact.
|
||||
|
||||
Part-DB ships some preview images for various common footprints like DIP-8 and others, as internal resources. These can
|
||||
be accessed/searched by typing the keyword in the URL field of a part and choosing one of the choices from the dropdown.
|
||||
|
||||
### Preview image / attachment
|
||||
Most entities with attachments allow you to select one of the defined attachments as "Preview image". You can select an image attachment here, that previews the entity, this could be a picture of a Part, the logo of a manufacturer or supplier, the schematic symbol of a category or the image of an footprint.
|
||||
|
||||
Most entities with attachments allow you to select one of the defined attachments as "Preview image". You can select an
|
||||
image attachment here, that previews the entity, this could be a picture of a Part, the logo of a manufacturer or
|
||||
supplier, the schematic symbol of a category or the image of a footprint.
|
||||
The preview image will be shown in various locations together with the entities name.
|
||||
|
||||
Please note that as long as the picture is not secret, it should be stored on the Part-DB instance (by upload, or letting Part-DB download the file) and *not* be marked as a private attachments, so that thumbnails can be generated for the picture (which improves performance).
|
||||
|
||||
Please note that as long as the picture is not secret, it should be stored on the Part-DB instance (by upload, or
|
||||
letting Part-DB download the file) and *not* be marked as a private attachments, so that thumbnails can be generated for
|
||||
the picture (which improves performance).
|
||||
|
||||
### Attachment types
|
||||
Attachment types define logical groups of attachments. For example you could define an attachment group "Datasheets" where all datasheets of Parts, Footprints, etc. belong in, "Pictures" for preview images and more.
|
||||
|
||||
Attachment types define logical groups of attachments. For example, you could define an attachment group "Datasheets"
|
||||
where all datasheets of Parts, Footprints, etc. belong in, "Pictures" for preview images and more.
|
||||
You can define file type restrictions, which file types and extensions are allowed for files with that attachment type.
|
||||
|
||||
## User System
|
||||
### User
|
||||
Each person which should be able to use Part-DB (by logging in) is represented by an user entity, which defines things like access rights, the password, and other things. For security reasons, every person which will use Part-DB should use its own personal account with an secret password. This allows to track activity of the users via the log.
|
||||
|
||||
There is a special user called `anonymous`, whose access rights are used to determine what an non-logged in user can do. Normally the anonymous user should be the most restricted user.
|
||||
### User
|
||||
|
||||
Each person which should be able to use Part-DB (by logging in) is represented by a user entity, which defines things
|
||||
like access rights, the password, and other things. For security reasons, every person which will use Part-DB should use
|
||||
its own personal account with a secret password. This allows to track activity of the users via the log.
|
||||
|
||||
There is a special user called `anonymous`, whose access rights are used to determine what a non-logged in user can do.
|
||||
Normally the anonymous user should be the most restricted user.
|
||||
|
||||
For simplification of access management users can be assigned to groups.
|
||||
|
||||
### Group
|
||||
A group is entity, to which users can be assigned to. This can be used to logically group users by for example organisational structures and to simplify permissions managment, as you can define groups with access rights for common use cases and then just assign users to them, without the need to change every permission on the users individually.
|
||||
|
||||
A group is entity, to which users can be assigned to. This can be used to logically group users by for example
|
||||
organisational structures and to simplify permissions management, as you can define groups with access rights for common
|
||||
use cases and then just assign users to them, without the need to change every permission on the users individually.
|
||||
|
||||
## Labels
|
||||
### Label profiles
|
||||
A label profile represents an template for a label (for a storelocation, a part or part lot). It consists of a size, an barcode type and the content. There are various placeholders which can be inserted in the text content and which will be used replaced with data for the actual thing.
|
||||
|
||||
You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label dialog), however if you want to generate many labels, it is recommended to save the settings as label profile, to save it for later usage. This ensures that all generated labels look the same.
|
||||
### Label profiles
|
||||
|
||||
A label profile represents a template for a label (for a storage location, a part or part lot). It consists of a size, a
|
||||
barcode type and the content. There are various placeholders which can be inserted in the text content and which will be
|
||||
used replaced with data for the actual thing.
|
||||
|
||||
You do not have to define a label profile to generate labels (you can just set the settings on the fly in the label
|
||||
dialog), however if you want to generate many labels, it is recommended to save the settings as label profile, to save
|
||||
it for later usage. This ensures that all generated labels look the same.
|
||||
@@ -6,107 +6,218 @@ nav_order: 5
|
||||
|
||||
# Configuration
|
||||
|
||||
Part-DBs behavior can be configured to your needs. There are different kind of configuration options: Options which are user changable (changable dynamically via frontend), options which can be configured by environment variables, and options which are only configurable via symfony config files.
|
||||
Part-DBs behavior can be configured to your needs. There are different kind of configuration options: Options which are
|
||||
user changeable (changeable dynamically via frontend), options which can be configured by environment variables, and
|
||||
options which are only configurable via symfony config files.
|
||||
|
||||
## User changable
|
||||
Following things can be changed for every user and a user can change it for himself (if he has the correct permission for it). Configuration is either possible via the users own setting page (where you can also change the password) or via the user admin page:
|
||||
* **Language**: The language that the users prefers, and which will be used when no language is explicitly specified. Language can still always be changed via the language selector. By default the global configured language is used.
|
||||
* **Timezone**: The timezone which the user resides in and in which all dates and times should be shown. By default the globally configured language.
|
||||
## User changeable
|
||||
|
||||
Following things can be changed for every user and a user can change it for himself (if he has the correct permission
|
||||
for it). Configuration is either possible via the users own setting page (where you can also change the password) or via
|
||||
the user admin page:
|
||||
|
||||
* **Language**: The language that the users prefers, and which will be used when no language is explicitly specified.
|
||||
Language can still always be changed via the language selector. By default, the global configured language is used.
|
||||
* **Timezone**: The timezone which the user resides in and in which all dates and times should be shown. By default, the
|
||||
globally configured language.
|
||||
* **Theme**: The theme to use for the frontend. Allows the user to choose the frontend design, he prefers.
|
||||
* **Prefered currency**: One of the defined currencies, in which all prices should be shown, if possible. Prices with other currencies will be converted to the price selected here
|
||||
* **Preferred currency**: One of the defined currencies, in which all prices should be shown, if possible. Prices with
|
||||
other currencies will be converted to the price selected here
|
||||
|
||||
## Environment variables (.env.local)
|
||||
The following configuration options can only be changed by the server administrator, by either changing the server variables, changing the `.env.local` file or setting env for your docker container. Here are just the most important options listed, see `.env` file for full list of possible env variables.
|
||||
|
||||
The following configuration options can only be changed by the server administrator, by either changing the server
|
||||
variables, changing the `.env.local` file or setting env for your docker container. Here are just the most important
|
||||
options listed, see `.env` file for full list of possible env variables.
|
||||
|
||||
### General options
|
||||
* `DATABASE_URL`: Configures the database which Part-DB uses. For mysql use a string in the form of `mysql://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<TABLE_NAME>` here (e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`. For sqlite use the following format to specify the absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`)
|
||||
* `DEFAULT_LANG`: The default language to use serverwide (when no language is explictly specified by a user or via language chooser). Must be something like `en`, `de`, `fr`, etc.
|
||||
* `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has not timezone specified. Must be something like `Europe/Berlin`. See [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) under TZ Database name for a list of available options.
|
||||
* `BASE_CURRENCY`: The currency to use internally for monetary values and when no currency is explictly specified. When migrating from a legacy Part-DB version, this should be the same as the currency in the old Part-DB instance (normally euro). This should be the currency you use the most. **Please note that you can not really change this setting after you have created data**. The value has to be a valid [ISO4217](https://en.wikipedia.org/wiki/ISO_4217) code, like `EUR` or `USD`.
|
||||
* `INSTANCE_NAME`: The name of your installation. It will be shown as a title in the navbar and other places. By default `Part-DB`, but you can customize it to something likes `ExampleCorp. Inventory`.
|
||||
* `ALLOW_ATTACHMENT_DOWNLOADS` (allowed values `0` or `1`): By setting this option to 1, users can make Part-DB directly download a file specified as an URL and create it as local file. Please not that this allows users access to all ressources publicly available to the server (so full access to other servers in the same local network), which could be a security risk.
|
||||
* `USE_GRAVATAR`: Set to `1` to use [gravatar.com](gravatar.com) images for user avatars (as long as they have not set their own picture). The users browsers have to download the pictures from a third-party (gravatars) server, so this might be a privacy risk.
|
||||
* `MAX_ATTACHMENT_FILE_SIZE`: The maximum file size (in bytes) for attachments. You can use the suffix `K`, `M` or `G` to specify the size in kilobytes, megabytes or gigabytes. By default `100M` (100 megabytes). Please note that this only the limit of Part-DB. You still need to configure the php.ini `upload_max_filesize` and `post_max_size` to allow bigger files to be uploaded.
|
||||
* `DEFAULT_URI`: The default URI base to use for the Part-DB, when no URL can be determined from the browser request. This should be the primary URL/Domain, which is used to access Part-DB. This value is used to create correct links in emails and other places, where the URL is needed. It is also used, when SAML is enabled.s If you are using a reverse proxy, you should set this to the URL of the reverse proxy (e.g. `https://part-db.example.com`). **This value must end with a slash**.
|
||||
* `ENFORCE_CHANGE_COMMENTS_FOR`: With this option you can configure, where users are enforced to give a change reason, which will be written to the log. This is a comma separated list of values (e.g. `part_edit,part_delete`). Leave empty to make change comments optional everywhere. Possible values are:
|
||||
* `part_edit`: Edit operation of a existing part
|
||||
* `part_delete`: Delete operation of a existing part
|
||||
* `part_create`: Creation of a new part
|
||||
* `part_stock_operation`: Stock operation on a part (therefore withdraw, add or move stock)
|
||||
* `datastructure_edit`: Edit operation of a existing datastructure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_delete`: Delete operation of a existing datastructure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...)
|
||||
* `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new versions, or if your server can not connect to the internet.
|
||||
|
||||
* `DATABASE_URL`: Configures the database which Part-DB uses. For mysql use a string in the form
|
||||
of `mysql://<USERNAME>:<PASSWORD>@<HOST>:<PORT>/<TABLE_NAME>` here
|
||||
(e.g. `DATABASE_URL=mysql://user:password@127.0.0.1:3306/part-db`). For sqlite use the following format to specify the
|
||||
absolute path where it should be located `sqlite:///path/part/app.db`. You can use `%kernel.project_dir%` as
|
||||
placeholder for the Part-DB root folder (e.g. `sqlite:///%kernel.project_dir%/var/app.db`)
|
||||
* `DEFAULT_LANG`: The default language to use server wide (when no language is explicitly specified by a user or via
|
||||
language chooser). Must be something like `en`, `de`, `fr`, etc.
|
||||
* `DEFAULT_TIMEZONE`: The default timezone to use globally, when a user has no timezone specified. Must be something
|
||||
like `Europe/Berlin`. See [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) under TZ Database name
|
||||
for a list of available options.
|
||||
* `BASE_CURRENCY`: The currency to use internally for monetary values and when no currency is explicitly specified. When
|
||||
migrating from a legacy Part-DB version, this should be the same as the currency in the old Part-DB instance (normally
|
||||
euro). This should be the currency you use the most. **Please note that you can not really change this setting after
|
||||
you have created data**. The value has to be a valid [ISO4217](https://en.wikipedia.org/wiki/ISO_4217) code,
|
||||
like `EUR` or `USD`.
|
||||
* `INSTANCE_NAME`: The name of your installation. It will be shown as a title in the navbar and other places. By
|
||||
default `Part-DB`, but you can customize it to something likes `ExampleCorp. Inventory`.
|
||||
* `ALLOW_ATTACHMENT_DOWNLOADS` (allowed values `0` or `1`): By setting this option to 1, users can make Part-DB directly
|
||||
download a file specified as a URL and create it as local file. Please note that this allows users access to all
|
||||
resources publicly available to the server (so full access to other servers in the same local network), which could
|
||||
be a security risk.
|
||||
* `USE_GRAVATAR`: Set to `1` to use [gravatar.com](https://gravatar.com/) images for user avatars (as long as they have
|
||||
not set their own picture). The users browsers have to download the pictures from a third-party (gravatar) server, so
|
||||
this might be a privacy risk.
|
||||
* `MAX_ATTACHMENT_FILE_SIZE`: The maximum file size (in bytes) for attachments. You can use the suffix `K`, `M` or `G`
|
||||
to specify the size in kilobytes, megabytes or gigabytes. By default `100M` (100 megabytes). Please note that this
|
||||
only the limit of Part-DB. You still need to configure the php.ini `upload_max_filesize` and `post_max_size` to allow
|
||||
bigger files to be uploaded.
|
||||
* `DEFAULT_URI`: The default URI base to use for the Part-DB, when no URL can be determined from the browser request.
|
||||
This should be the primary URL/Domain, which is used to access Part-DB. This value is used to create correct links in
|
||||
emails and other places, where the URL is needed. It is also used, when SAML is enabled.s If you are using a reverse
|
||||
proxy, you should set this to the URL of the reverse proxy (e.g. `https://part-db.example.com`). **This value must end
|
||||
with a slash**.
|
||||
* `ENFORCE_CHANGE_COMMENTS_FOR`: With this option you can configure, where users are enforced to give a change reason,
|
||||
which will be written to the log. This is a comma separated list of values (e.g. `part_edit,part_delete`). Leave empty
|
||||
to make change comments optional everywhere. Possible values are:
|
||||
* `part_edit`: Edit operation of an existing part
|
||||
* `part_delete`: Delete operation of an existing part
|
||||
* `part_create`: Creation of a new part
|
||||
* `part_stock_operation`: Stock operation on a part (therefore withdraw, add or move stock)
|
||||
* `datastructure_edit`: Edit operation of an existing datastructure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_delete`: Delete operation of a existing datastructure (e.g. category, manufacturer, ...)
|
||||
* `datastructure_create`: Creation of a new datastructure (e.g. category, manufacturer, ...)
|
||||
* `CHECK_FOR_UPDATES` (default `1`): Set this to 0, if you do not want Part-DB to connect to GitHub to check for new
|
||||
versions, or if your server can not connect to the internet.
|
||||
|
||||
### E-Mail settings
|
||||
* `MAILER_DSN`: You can configure the mail provider which should be used for email delivery (see https://symfony.com/doc/current/components/mailer.html for full documentation). If you just want to use an SMTP mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587`
|
||||
* `EMAIL_SENDER_EMAIL`: The email address from which emails should be sent from (in most cases this has to be the same as the email address used for SMTP access)
|
||||
* `EMAIL_SENDER_NAME`: Similar to `EMAIL_SENDER_EMAIL` but this allows you to specify the name from which the mails are sent from.
|
||||
* `ALLOW_EMAIL_PW_RESET`: Set this value to true, if you wan to allow users to reset their password via an email notification. You have to configure the mailprovider first before via the MAILER_DSN setting.
|
||||
|
||||
* `MAILER_DSN`: You can configure the mail provider which should be used for email delivery (
|
||||
see https://symfony.com/doc/current/components/mailer.html for full documentation). If you just want to use an SMTP
|
||||
mail account, you can use the following syntax `MAILER_DSN=smtp://user:password@smtp.mailserver.invalid:587`
|
||||
* `EMAIL_SENDER_EMAIL`: The email address from which emails should be sent from (in most cases this has to be the same
|
||||
as the email address used for SMTP access)
|
||||
* `EMAIL_SENDER_NAME`: Similar to `EMAIL_SENDER_EMAIL` but this allows you to specify the name from which the mails are
|
||||
sent from.
|
||||
* `ALLOW_EMAIL_PW_RESET`: Set this value to true, if you want to allow users to reset their password via an email
|
||||
notification. You have to configure the mailprovider first before via the MAILER_DSN setting.
|
||||
|
||||
### Table related settings
|
||||
* `TABLE_DEFAULT_PAGE_SIZE`: The default page size for tables. This is the number of rows which are shown per page. Set to `-1` to disable pagination and show all rows at once.
|
||||
|
||||
* `TABLE_DEFAULT_PAGE_SIZE`: The default page size for tables. This is the number of rows which are shown per page. Set
|
||||
to `-1` to disable pagination and show all rows at once.
|
||||
* `TABLE_PARTS_DEFAULT_COLUMNS`: The columns in parts tables, which are visible by default (when loading table for first
|
||||
time).
|
||||
Also specify the default order of the columns. This is a comma separated list of column names. Available columns
|
||||
are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`.
|
||||
|
||||
### History/Eventlog related settings
|
||||
The following options are used to configure, which (and how much) data is written to the system log:
|
||||
* `HISTORY_SAVE_CHANGED_FIELDS`: When this option is set to true, the name of the fields which are changed, are saved to the DB (so for example it is logged that a user has changed, that the user has changed the name and description of the field, but not the data/content of these changes)
|
||||
* `HISTORY_SAVE_CHANGED_DATA`: When this option is set to true, the changed data is saved to log (so it is logged, that a user has changed the name of a part and what the name was before). This can increase database size, when you have a lot of changes to entities.
|
||||
* `HISTORY_SAVE_REMOVED_DATA`: When this option is set to true, removed data is saved to log, meaning that you can easily undelete an entity, when it was removed accidentally.
|
||||
* `HISTORY_SAVE_NEW_DATA`: When this option is set to true, the new data (the data after a change) is saved to element changed log entries. This allows you to easily see the changes between two revisions of an entity. This can increase database size, when you have a lot of changes to entities.
|
||||
|
||||
If you wanna use want to revert changes or view older revisions of entities, then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAVE_REMOVED_DATA` all have to be true.
|
||||
The following options are used to configure, which (and how much) data is written to the system log:
|
||||
|
||||
* `HISTORY_SAVE_CHANGED_FIELDS`: When this option is set to true, the name of the fields which are changed, are saved to
|
||||
the DB (so for example it is logged that a user has changed, that the user has changed the name and description of the
|
||||
field, but not the data/content of these changes)
|
||||
* `HISTORY_SAVE_CHANGED_DATA`: When this option is set to true, the changed data is saved to log (so it is logged, that
|
||||
a user has changed the name of a part and what the name was before). This can increase database size, when you have a
|
||||
lot of changes to entities.
|
||||
* `HISTORY_SAVE_REMOVED_DATA`: When this option is set to true, removed data is saved to log, meaning that you can
|
||||
easily undelete an entity, when it was removed accidentally.
|
||||
* `HISTORY_SAVE_NEW_DATA`: When this option is set to true, the new data (the data after a change) is saved to element
|
||||
changed log entries. This allows you to easily see the changes between two revisions of an entity. This can increase
|
||||
database size, when you have a lot of changes to entities.
|
||||
|
||||
If you want to use want to revert changes or view older revisions of entities,
|
||||
then `HISTORY_SAVE_CHANGED_FIELDS`, `HISTORY_SAVE_CHANGED_DATA` and `HISTORY_SAVE_REMOVED_DATA` all have to be true.
|
||||
|
||||
### Error pages settings
|
||||
* `ERROR_PAGE_ADMIN_EMAIL`: You can set an email-address here, which is shown on the error page, who should be contacted about the issue (e.g. an IT support email of your company)
|
||||
* `ERROR_PAGE_SHOW_HELP`: Set this 0, to disable the solution hints shown on an error page. These hints should not contain senstive informations, but could confuse end-users.
|
||||
|
||||
* `ERROR_PAGE_ADMIN_EMAIL`: You can set an email-address here, which is shown on the error page, who should be contacted
|
||||
about the issue (e.g. an IT support email of your company)
|
||||
* `ERROR_PAGE_SHOW_HELP`: Set this 0, to disable the solution hints shown on an error page. These hints should not
|
||||
contain sensitive information, but could confuse end-users.
|
||||
|
||||
### SAML SSO settings
|
||||
The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to login to Part-DB without entering a username and password, but instead they are redirected to a SAML Identity Provider (IdP) and are logged in automatically. This is especially useful, when you want to use Part-DB in a company, where all users have a SAML account (e.g. via Active Directory or LDAP).
|
||||
You can find more advanced settings in the `config/packages/hslavich_onelogin_saml.yaml` file. Please note that this file is not backuped by the backup script, so you have to backup it manually, if you want to keep your changes. If you want to edit it on docker, you have to map the file to a volume.
|
||||
|
||||
* `SAML_ENABLED`: When this is set to 1, SAML SSO is enabled and the SSO Login button is shown in the login form. You have to configure the SAML settings below, before you can use this feature.
|
||||
* `SAML_ROLE_MAPPING`: A [JSON](https://en.wikipedia.org/wiki/JSON) encoded map which specifies how Part-DB should convert the user roles given by SAML attribute `group` should be converted to a Part-DB group (specified by ID). You can use a wildcard `*` to map all otherwise unmapped roles to a certain group. Example: `{"*": 1, "admin": 2, "editor": 3}`. This would map all roles to the group with ID 1, except the role `admin`, which is mapped to the group with ID 2 and the role `editor`, which is mapped to the group with ID 3.
|
||||
* `SAML_UPDATE_GROUP_ON_LOGIN`: When this is enabled the group of the user is updated on every login of the user based on the SAML role attributes. When this is disabled, the group is only assigned on the first login of the user, and a Part-DB administrator can change the group afterwards by editing the user.
|
||||
* `SAML_IDP_ENTITY_ID`: The entity ID of your SAML Identity Provider (IdP). You can find this value in the metadata XML file or configuration UI of your IdP.
|
||||
* `SAML_IDP_SINGLE_SIGN_ON_SERVICE`: The URL of the SAML IdP Single Sign-On Service (SSO). You can find this value in the metadata XML file or configuration UI of your IdP.
|
||||
* `SAML_IDP_SINGLE_LOGOUT_SERVICE`: The URL of the SAML IdP Single Logout Service (SLO). You can find this value in the metadata XML file or configuration UI of your IdP.
|
||||
* `SAML_IDP_X509_CERT`: The base64 encoded X.509 public certificate of your SAML IdP. You can find this value in the metadata XML file or configuration UI of your IdP. It should start with `MIIC` and end with `=`.
|
||||
* `SAML_SP_ENTITY_ID`: The entity ID of your SAML Service Provider (SP). This is the value you have configured for the Part-DB client in your IdP.
|
||||
* `SAML_SP_X509_CERT`: The public X.509 certificate of your SAML SP (here Part-DB). This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIC` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_PRIVATE_KEY` setting.
|
||||
* `SAML_SP_PRIVATE_KEY`: The private key of your SAML SP (here Part-DB), corresponding the public key specified in `SAML_SP_X509_CERT`. This is the value you have configured for the Part-DB client in your IdP. It should start with `MIIE` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which you can setup here and in the `SAML_SP_X509_CERT` setting.
|
||||
The following settings can be used to enable and configure Single-Sign on via SAML. This allows users to log in to
|
||||
Part-DB without entering a username and password, but instead they are redirected to a SAML Identity Provider (IdP) and
|
||||
are logged in automatically. This is especially useful, when you want to use Part-DB in a company, where all users have
|
||||
a SAML account (e.g. via Active Directory or LDAP).
|
||||
You can find more advanced settings in the `config/packages/hslavich_onelogin_saml.yaml` file. Please note that this
|
||||
file is not backed up by the backup script, so you have to back up it manually, if you want to keep your changes. If you
|
||||
want to edit it on docker, you have to map the file to a volume.
|
||||
|
||||
* `SAML_ENABLED`: When this is set to 1, SAML SSO is enabled and the SSO Login button is shown in the login form. You
|
||||
have to configure the SAML settings below, before you can use this feature.
|
||||
* `SAML_BEHIND_PROXY`: Set this to 1, if Part-DB is behind a reverse proxy. See [here]({% link installation/reverse-proxy.md %})
|
||||
for more information. Otherwise, leave it to 0 (default.)
|
||||
* `SAML_ROLE_MAPPING`: A [JSON](https://en.wikipedia.org/wiki/JSON) encoded map which specifies how Part-DB should
|
||||
convert the user roles given by SAML attribute `group` should be converted to a Part-DB group (specified by ID). You
|
||||
can use a wildcard `*` to map all otherwise unmapped roles to a certain group.
|
||||
Example: `{"*": 1, "admin": 2, "editor": 3}`. This would map all roles to the group with ID 1, except the
|
||||
role `admin`, which is mapped to the group with ID 2 and the role `editor`, which is mapped to the group with ID 3.
|
||||
* `SAML_UPDATE_GROUP_ON_LOGIN`: When this is enabled the group of the user is updated on every login of the user based
|
||||
on the SAML role attributes. When this is disabled, the group is only assigned on the first login of the user, and a
|
||||
Part-DB administrator can change the group afterward by editing the user.
|
||||
* `SAML_IDP_ENTITY_ID`: The entity ID of your SAML Identity Provider (IdP). You can find this value in the metadata XML
|
||||
file or configuration UI of your IdP.
|
||||
* `SAML_IDP_SINGLE_SIGN_ON_SERVICE`: The URL of the SAML IdP Single Sign-On Service (SSO). You can find this value in
|
||||
the metadata XML file or configuration UI of your IdP.
|
||||
* `SAML_IDP_SINGLE_LOGOUT_SERVICE`: The URL of the SAML IdP Single Logout Service (SLO). You can find this value in the
|
||||
metadata XML file or configuration UI of your IdP.
|
||||
* `SAML_IDP_X509_CERT`: The base64 encoded X.509 public certificate of your SAML IdP. You can find this value in the
|
||||
metadata XML file or configuration UI of your IdP. It should start with `MIIC` and end with `=`.
|
||||
* `SAML_SP_ENTITY_ID`: The entity ID of your SAML Service Provider (SP). This is the value you have configured for the
|
||||
Part-DB client in your IdP.
|
||||
* `SAML_SP_X509_CERT`: The public X.509 certificate of your SAML SP (here Part-DB). This is the value you have
|
||||
configured for the Part-DB client in your IdP. It should start with `MIIC` and end with `=`. IdPs like keycloak allows
|
||||
you to generate a public/private key pair for the client which you can set up here and in the `SAML_SP_PRIVATE_KEY`
|
||||
setting.
|
||||
* `SAML_SP_PRIVATE_KEY`: The private key of your SAML SP (here Part-DB), corresponding the public key specified
|
||||
in `SAML_SP_X509_CERT`. This is the value you have configured for the Part-DB client in your IdP. It should start
|
||||
with `MIIE` and end with `=`. IdPs like keycloak allows you to generate a public/private key pair for the client which
|
||||
you can set up here and in the `SAML_SP_X509_CERT` setting.
|
||||
|
||||
### Information provider settings
|
||||
|
||||
The settings prefixes with `PROVIDER_*` are used to configure the information providers.
|
||||
See the [information providers]({% link usage/information_provider_system.md %}) page for more information.
|
||||
|
||||
### Other / less used options
|
||||
* `TRUSTED_PROXIES`: Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct IP informations (see [here](https://symfony.com/doc/current/deployment/proxies.html) for more info).
|
||||
* `TRUSTED_HOSTS`: To prevent `HTTP Host header attacks` you can set a regex containing all host names via which Part-DB should be accessible. If accessed via the wrong hostname, an error will be shown.
|
||||
* `DEMO_MODE`: Set Part-DB into demo mode, which forbids users to change their passwords and settings. Used for the demo instance, should not be needed for normal installations.
|
||||
* `NO_URL_REWRITE_AVAILABLE` (allowed values `true` or `false`): Set this value to true, if your webserver does not support rewrite. In this case, all URL pathes will contain index.php/, which is needed then. Normally this setting do not need to be changed.
|
||||
* `FIXER_API_KEY`: If you want to automatically retrieve exchange rates for base currencies other than euros, you have configure an exchange rate provider API. [Fixer.io](https://fixer.io/) is preconfigured, and you just have to register there and set the retrieved API key in this environment variable.
|
||||
* `APP_ENV`: This value should always be set to `prod` in normal use. Set it to `dev` to enable debug/development mode. (**You should not do this on a publicly accessible server, as it will leak sensitive informations!**)
|
||||
* `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker container. In all other applications you can just change the `config/banner.md` file.
|
||||
|
||||
* `TRUSTED_PROXIES`: Set the IP addresses (or IP blocks) of trusted reverse proxies here. This is needed to get correct
|
||||
IP information (see [here](https://symfony.com/doc/current/deployment/proxies.html) for more info).
|
||||
* `TRUSTED_HOSTS`: To prevent `HTTP Host header attacks` you can set a regex containing all host names via which Part-DB
|
||||
should be accessible. If accessed via the wrong hostname, an error will be shown.
|
||||
* `DEMO_MODE`: Set Part-DB into demo mode, which forbids users to change their passwords and settings. Used for the demo
|
||||
instance, should not be needed for normal installations.
|
||||
* `NO_URL_REWRITE_AVAILABLE` (allowed values `true` or `false`): Set this value to true, if your webserver does not
|
||||
support rewrite. In this case, all URL paths will contain index.php/, which is needed then. Normally this setting do
|
||||
not need to be changed.
|
||||
* `FIXER_API_KEY`: If you want to automatically retrieve exchange rates for base currencies other than euros, you have to
|
||||
configure an exchange rate provider API. [Fixer.io](https://fixer.io/) is preconfigured, and you just have to register
|
||||
there and set the retrieved API key in this environment variable.
|
||||
* `APP_ENV`: This value should always be set to `prod` in normal use. Set it to `dev` to enable debug/development
|
||||
mode. (**You should not do this on a publicly accessible server, as it will leak sensitive information!**)
|
||||
* `BANNER`: You can configure the text that should be shown as the banner on the homepage. Useful especially for docker
|
||||
container. In all other applications you can just change the `config/banner.md` file.
|
||||
|
||||
## Banner
|
||||
|
||||
To change the banner you can find on the homepage, you can either set the `BANNER` environment variable to the text you
|
||||
want to show, or you can edit the `config/banner.md` file. The banner is written in markdown, so you can use all
|
||||
markdown (and even some subset of HTML) syntax to format the text.
|
||||
|
||||
## parameters.yaml
|
||||
You can also configure some options via the `config/parameters.yaml` file. This should normally not needed,
|
||||
and you should know what you are doing, when you change something here. You should expect, that you will have to do some
|
||||
manual merge, when you have changed something here and update to a newer version of Part-DB. It is possible that configuration
|
||||
|
||||
You can also configure some options via the `config/parameters.yaml` file. This should normally not need,
|
||||
and you should know what you are doing, when you change something here. You should expect, that you will have to do some
|
||||
manual merge, when you have changed something here and update to a newer version of Part-DB. It is possible that
|
||||
configuration
|
||||
options here will change or completely removed in future versions of Part-DB.
|
||||
|
||||
If you change something here, you have to clear the cache, before the changes will take effect with the command `bin/console cache:clear`.
|
||||
If you change something here, you have to clear the cache, before the changes will take effect with the
|
||||
command `bin/console cache:clear`.
|
||||
|
||||
The following options are available:
|
||||
|
||||
* `partdb.global_theme`: The default theme to use, when no user specific theme is set. Should be one of the themes from the `partdb.available_themes` config option.
|
||||
* `partdb.locale_menu`: The codes of the languages, which should be shown in the language chooser menu (the one with the user icon in the navbar). The first language in the list will be the default language.
|
||||
* `partdb.gdpr_compliance`: When set to true (default value), IP addresses which are saved in the database will be anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU.
|
||||
* `partdb.sidebar.items`: The panel contents which should be shown in the sidebar by default. You can also change the number of sidebar panels by changing the number of items in this list.
|
||||
* `partdb.global_theme`: The default theme to use, when no user specific theme is set. Should be one of the themes from
|
||||
the `partdb.available_themes` config option.
|
||||
* `partdb.locale_menu`: The codes of the languages, which should be shown in the language chooser menu (the one with the
|
||||
user icon in the navbar). The first language in the list will be the default language.
|
||||
* `partdb.gdpr_compliance`: When set to true (default value), IP addresses which are saved in the database will be
|
||||
anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in
|
||||
the EU.
|
||||
* `partdb.sidebar.items`: The panel contents which should be shown in the sidebar by default. You can also change the
|
||||
number of sidebar panels by changing the number of items in this list.
|
||||
* `partdb.sidebar.root_node_enable`: Show a root node in the sidebar trees, of which all nodes are children of
|
||||
* `partdb.sidebar.root_expanded`: Expand the root node in the sidebar trees by default
|
||||
* `partdb.available_themes`: The list of available themes a user can choose from.
|
||||
@@ -5,39 +5,50 @@ nav_order: 0
|
||||
---
|
||||
|
||||
# Part-DB
|
||||
|
||||
Part-DB is an Open-Source inventory management system for your electronic components.
|
||||
It is installed on a web server and so can be accessed with any browser without the need to install additional software.
|
||||
|
||||
{: .important-title }
|
||||
> Demo
|
||||
>
|
||||
> If you want to test Part-DB without installing it, you can use [this](https://part-db.herokuapp.com) Heroku instance.
|
||||
> (Or this link for the [German Version](https://part-db.herokuapp.com/de/)).
|
||||
>
|
||||
> If you want to test Part-DB without installing it, you can use [this](https://demo.part-db.de/) Heroku instance.
|
||||
> (Or this link for the [German Version](https://demo.part-db.de/de/)).
|
||||
>
|
||||
> You can log in with username: **user** and password: **user**, to change/create data.
|
||||
>
|
||||
> Every change to the master branch gets automatically deployed, so it represents the currenct development progress and is
|
||||
> maybe not completly stable. Please mind, that the free Heroku instance is used, so it can take some time when loading the page
|
||||
> Every change to the master branch gets automatically deployed, so it represents the current development progress and
|
||||
> is
|
||||
> maybe not completely stable. Please mind, that the free Heroku instance is used, so it can take some time when loading
|
||||
> the page
|
||||
> for the first time.
|
||||
|
||||
## Features
|
||||
|
||||
* Inventory management of your electronic parts. Each part can be assigned to a category, footprint, manufacturer
|
||||
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files like datasheets or pictures with the parts.
|
||||
and multiple store locations and price information. Parts can be grouped using tags. You can associate various files
|
||||
like datasheets or pictures with the parts.
|
||||
* Multi-Language support (currently German, English, Russian, Japanese and French (experimental))
|
||||
* Barcodes/Labels generator for parts and storage locations, scan barcodes via webcam using the builtin barcode scanner
|
||||
* User system with groups and detailed (fine granular) permissions.
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups. Password reset via email can be setuped.
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
Two-factor authentication is supported (Google Authenticator and Webauthn/U2F keys) and can be enforced for groups.
|
||||
Password reset via email can be setup.
|
||||
* Optional support for single sign-on (SSO) via SAML (using an intermediate service
|
||||
like [Keycloak](https://www.keycloak.org/) you can connect Part-DB to an existing LDAP or Active Directory server)
|
||||
* Import/Export system
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older versions.
|
||||
* Project management: Create projects and assign parts to the bill of material (BOM), to show how often you could build
|
||||
this project and directly withdraw all components needed from DB
|
||||
* Event log: Track what changes happens to your inventory, track which user does what. Revert your parts to older
|
||||
versions.
|
||||
* Responsive design: You can use Part-DB on your PC, your tablet and your smartphone using the same interface.
|
||||
* MySQL and SQLite supported as database backends
|
||||
* Support for rich text descriptions and comments in parts
|
||||
* Support for multiple currencies and automatic update of exchange rates supported
|
||||
* Powerful search and filter function, including parametric search (search for parts according to some specifications)
|
||||
* Easy migration from an existing PartKeepr instance (see [here]({%link partkeepr_migration.md %}))
|
||||
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and prices for parts (see [here]({% link usage/information_provider_system.md %}))
|
||||
* Use cloud providers (like Octopart, Digikey, farnell or TME) to automatically get part information, datasheets and
|
||||
prices for parts (see [here]({% link usage/information_provider_system.md %}))
|
||||
* API to access Part-DB from other applications/scripts
|
||||
|
||||
With these features Part-DB is useful to hobbyists, who want to keep track of their private electronic parts inventory,
|
||||
or makerspaces, where many users have should have (controlled) access to the shared inventory.
|
||||
@@ -45,25 +56,31 @@ or makerspaces, where many users have should have (controlled) access to the sha
|
||||
Part-DB is also used by small companies and universities for managing their inventory.
|
||||
|
||||
## License
|
||||
|
||||
Part-DB is licensed under the GNU Affero General Public License v3.0 (or at your opinion any later).
|
||||
This mostly means that you can use Part-DB for whatever you want (even use it commercially)
|
||||
as long as you publish the source code for every change you make under the AGPL, too.
|
||||
|
||||
See [LICENSE](https://github.com/Part-DB/Part-DB-symfony/blob/master/LICENSE) for more informations.
|
||||
See [LICENSE](https://github.com/Part-DB/Part-DB-symfony/blob/master/LICENSE) for more information.
|
||||
|
||||
## Donate for development
|
||||
|
||||
If you want to donate to the Part-DB developer, see the sponsor button in the top bar (next to the repo name).
|
||||
There you will find various methods to support development on a monthly or a one time base.
|
||||
|
||||
## Built with
|
||||
|
||||
* [Symfony 5](https://symfony.com/): The main framework used for the serverside PHP
|
||||
* [Bootstrap 5](https://getbootstrap.com/) and [Bootswatch](https://bootswatch.com/): Used as website theme
|
||||
* [Fontawesome](https://fontawesome.com/): Used as icon set
|
||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend Javascript
|
||||
* [Hotwire Stimulus](https://stimulus.hotwired.dev/) and [Hotwire Turbo](https://turbo.hotwired.dev/): Frontend
|
||||
Javascript
|
||||
|
||||
## Authors
|
||||
* **Jan Böhmer** - *Inital work and Maintainer* - [Github](https://github.com/jbtronics/)
|
||||
|
||||
See also the list of [contributors](https://github.com/Part-DB/Part-DB-symfony/graphs/contributors) who participated in this project.
|
||||
* **Jan Böhmer** - *Initial work and Maintainer* - [GitHub](https://github.com/jbtronics/)
|
||||
|
||||
See also the list of [contributors](https://github.com/Part-DB/Part-DB-symfony/graphs/contributors) who participated in
|
||||
this project.
|
||||
|
||||
Based on the original Part-DB by Christoph Lechner and K. Jacobs
|
||||
|
||||
@@ -7,24 +7,38 @@ nav_order: 1
|
||||
|
||||
# Choosing database: SQLite or MySQL
|
||||
|
||||
Part-DB saves its data in a [relational (SQL) database](https://en.wikipedia.org/wiki/Relational_database). Part-DB supports either the use of [SQLite](https://www.sqlite.org/index.html) or [MySQL](https://www.mysql.com/) / [MariaDB](https://mariadb.org/) (which are mostly the same, except for some minor differences).
|
||||
Part-DB saves its data in a [relational (SQL) database](https://en.wikipedia.org/wiki/Relational_database). Part-DB
|
||||
supports either the use of [SQLite](https://www.sqlite.org/index.html)
|
||||
or [MySQL](https://www.mysql.com/) / [MariaDB](https://mariadb.org/) (which are mostly the same, except for some minor
|
||||
differences).
|
||||
|
||||
{: .important }
|
||||
You have to choose between the database types before you start using Part-DB and **you can not change it (easily) after you have started creating data**. So you should choose the database type for your usecase (and possible future uses).
|
||||
You have to choose between the database types before you start using Part-DB and **you can not change it (easily) after
|
||||
you have started creating data**. So you should choose the database type for your use case (and possible future uses).
|
||||
|
||||
## Comparison
|
||||
|
||||
**SQLite** is the default database type which is configured out of the box. All data is saved in a single file (normally `var/app.db` in the Part-DB folder) and no additional installation or configuration besides Part-DB is needed.
|
||||
To use **MySQL/MariaDB** as database, you have to install and configure the MySQL server, configure it and create a database and user for Part-DB, which needs some additional work. When using docker you need an additional docker container, and volume for the data
|
||||
**SQLite** is the default database type which is configured out of the box. All data is saved in a single file (
|
||||
normally `var/app.db` in the Part-DB folder) and no additional installation or configuration besides Part-DB is needed.
|
||||
To use **MySQL/MariaDB** as database, you have to install and configure the MySQL server, configure it and create a
|
||||
database and user for Part-DB, which needs some additional work. When using docker you need an additional docker
|
||||
container, and volume for the data
|
||||
|
||||
When using **SQLite** The database can be backuped easily by just copying the SQLite file to a safe place. Ideally the **MySQL** database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can do this automatically
|
||||
|
||||
However SQLite does not support certain operations like regex search, which has to be emulated by PHP and therefore are pretty slow compared to the same operation at MySQL. In future there might be features that may only be available, when using MySQL.
|
||||
When using **SQLite** The database can be backuped easily by just copying the SQLite file to a safe place. Ideally the *
|
||||
*MySQL** database has to be dumped to a SQL file (using `mysqldump`). The `console partdb:backup` command can do this
|
||||
automatically
|
||||
|
||||
In general MySQL might perform better for big Part-DB instances with many entries, lots of users and high activity, than SQLite.
|
||||
However, SQLite does not support certain operations like regex search, which has to be emulated by PHP and therefore are
|
||||
pretty slow compared to the same operation at MySQL. In future there might be features that may only be available, when
|
||||
using MySQL.
|
||||
|
||||
In general MySQL might perform better for big Part-DB instances with many entries, lots of users and high activity, than
|
||||
SQLite.
|
||||
|
||||
## Conclusion and Suggestion
|
||||
|
||||
When you are a hobbyist and use Part-DB for your own small inventory managment with only you as user (or maybe sometimes a few other people), then the easy to use SQLite database will be fine.
|
||||
When you are a hobbyist and use Part-DB for your own small inventory management with only you as user (or maybe sometimes
|
||||
a few other people), then the easy-to-use SQLite database will be fine.
|
||||
|
||||
When you are planning to have a very big database, with a lot of entries and many users which regulary (and concurrently) using Part-DB you should maybe use MySQL as this will scale better.
|
||||
When you are planning to have a very big database, with a lot of entries and many users which regularly (and
|
||||
concurrently) using Part-DB you should maybe use MySQL as this will scale better.
|
||||
@@ -7,31 +7,34 @@ nav_order: 12
|
||||
|
||||
# Email
|
||||
|
||||
Part-DB can communicate with its users via email.
|
||||
Part-DB can communicate with its users via email.
|
||||
At the moment this is only used to send password reset links, but in future this will be used for other things too.
|
||||
|
||||
To make emails work you have to properly configure a mail provider in Part-DB.
|
||||
|
||||
## Configuration
|
||||
Part-DB uses [Symfony Mailer](https://symfony.com/doc/current/mailer.html) to send emails, which supports multiple
|
||||
automatic mail providers (like MailChimp or SendGrid). If you want to use one of these providers, check the Symfony Mailer documentation for more information.
|
||||
|
||||
We will only cover the configuration of a SMTP provider here, which is sufficient for most usecases.
|
||||
You will need an email account, which you can use send emails from via password-bases SMTP authentication, this account
|
||||
Part-DB uses [Symfony Mailer](https://symfony.com/doc/current/mailer.html) to send emails, which supports multiple
|
||||
automatic mail providers (like MailChimp or SendGrid). If you want to use one of these providers, check the Symfony
|
||||
Mailer documentation for more information.
|
||||
|
||||
We will only cover the configuration of an SMTP provider here, which is sufficient for most use-cases.
|
||||
You will need an email account, which you can use send emails from via password-bases SMTP authentication, this account
|
||||
should be dedicated to Part-DB.
|
||||
|
||||
To configure the SMTP provider, you have to set the following environment variables:
|
||||
|
||||
`MAILER_DSN`: You have to provide the SMTP server address and the credentials for the email account here. The format is the following:
|
||||
`smtp://<username>:<password>@<smtp-server-address>:<port>`. In most cases the username is the email address of the account, and the port is 587.
|
||||
`MAILER_DSN`: You have to provide the SMTP server address and the credentials for the email account here. The format is
|
||||
the following:
|
||||
`smtp://<username>:<password>@<smtp-server-address>:<port>`. In most cases the username is the email address of the
|
||||
account, and the port is 587.
|
||||
So the resulting DSN could look like this: `smtp://j.doe@mail.invalid:SUPER_SECRET_PA$$WORD@smtp.mail.invalid:587`.
|
||||
|
||||
`EMAIL_SENDER_EMAIL`: This is the email address which will be used as sender address for all emails sent by Part-DB.
|
||||
This should be the same email address as the one used in the `MAILER_DSN` (the email adress of your email account):
|
||||
This should be the same email address as the one used in the `MAILER_DSN` (the email address of your email account):
|
||||
e.g. `j.doe@mail.invalid`.
|
||||
|
||||
`EMAIL_SENDER_NAME`: This is the name which will be used as sender name for all emails sent by Part-DB.
|
||||
`EMAIL_SENDER_NAME`: This is the name which will be used as sender name for all emails sent by Part-DB.
|
||||
This can be anything you want, e.g. `My Part-DB Mailer`.
|
||||
|
||||
|
||||
Now you can enable the possibility to reset password by setting the `ALLOW_EMAIL_PW_RESET` env to `1` (or `true`).
|
||||
@@ -7,17 +7,18 @@ nav_order: 2
|
||||
|
||||
# Installation of Part-DB via docker
|
||||
|
||||
Part-DB can be installed containerized via docker. This is the easiest way to get Part-DB up and running and works on all platforms,
|
||||
where docker is available (especially recommended for Windows and MacOS).
|
||||
|
||||
Part-DB can be installed containerized via docker. This is the easiest way to get Part-DB up and running and works on
|
||||
all platforms,
|
||||
where docker is available (especially recommended for Windows and macOS).
|
||||
|
||||
{: .warning }
|
||||
> The methods described here, configure PHP without HTTPS and therefore should only be used locally in a trusted network.
|
||||
> The methods described here, configure PHP without HTTPS and therefore should only be used locally in a trusted
|
||||
> network.
|
||||
> If you want to expose Part-DB to the internet, you have to configure a reverse proxy with an SSL certificate!
|
||||
|
||||
## Docker-compose
|
||||
Docker-compose configures the needed images and automatically creates the needed containers and volumes.
|
||||
|
||||
Docker-compose configures the needed images and automatically creates the needed containers and volumes.
|
||||
|
||||
1. Install docker and docker-compose like described under https://docs.docker.com/compose/install/
|
||||
2. Create a folder where the Part-DB data should live
|
||||
@@ -68,19 +69,28 @@ services:
|
||||
# When this is empty the content of config/banner.md is used as banner
|
||||
#- BANNER=This is a test banner<br>with a line break
|
||||
```
|
||||
4. Customize the settings by changing the environment variables (or add new ones). See [Configuration]({% link configuration.md %}) for more information.
|
||||
|
||||
4. Customize the settings by changing the environment variables (or add new ones). See [Configuration]({% link
|
||||
configuration.md %}) for more information.
|
||||
5. Inside the folder, run
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
6. Create the inital database with
|
||||
|
||||
6. Create the initial database with
|
||||
|
||||
```bash
|
||||
docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate
|
||||
```
|
||||
and watch for the password output
|
||||
6. Part-DB is available under `http://localhost:8080` and you can log in with username `admin` and the password shown before
|
||||
|
||||
The docker image uses a SQLite database and all data (database, uploads and other media) is put into folders relative to the docker-compose.yml.
|
||||
and watch for the password output
|
||||
|
||||
6. Part-DB is available under `http://localhost:8080` and you can log in with username `admin` and the password shown
|
||||
before
|
||||
|
||||
The docker image uses a SQLite database and all data (database, uploads and other media) is put into folders relative to
|
||||
the docker-compose.yml.
|
||||
|
||||
### MySQL
|
||||
|
||||
@@ -140,6 +150,7 @@ services:
|
||||
database:
|
||||
container_name: partdb_database
|
||||
image: mysql:8.0
|
||||
restart: unless-stopped
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
environment:
|
||||
# Change this Password
|
||||
@@ -156,8 +167,10 @@ services:
|
||||
```
|
||||
|
||||
### Update Part-DB
|
||||
|
||||
You can update Part-DB by pulling the latest image and restarting the container.
|
||||
Then you have to run the database migrations again
|
||||
|
||||
```bash
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
@@ -165,19 +178,26 @@ docker exec --user=www-data partdb php bin/console doctrine:migrations:migrate
|
||||
```
|
||||
|
||||
## Direct use of docker image
|
||||
You can use the `jbtronics/part-db1:master` image directly. You have to expose the port 80 to a host port and configure volumes for `/var/www/html/uploads` and `/var/www/html/public/media`.
|
||||
|
||||
If you want to use SQLite database (which is default), you have to configure Part-DB to put the database file in a mapped volume via the `DATABASE_URL` environment variable.
|
||||
For example if you set `DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db` then you will have to map the `/var/www/html/var/db/` folder to the docker container (see docker-compose.yaml for example).
|
||||
You can use the `jbtronics/part-db1:master` image directly. You have to expose the port 80 to a host port and configure
|
||||
volumes for `/var/www/html/uploads` and `/var/www/html/public/media`.
|
||||
|
||||
If you want to use SQLite database (which is default), you have to configure Part-DB to put the database file in a
|
||||
mapped volume via the `DATABASE_URL` environment variable.
|
||||
For example if you set `DATABASE_URL=sqlite:///%kernel.project_dir%/var/db/app.db` then you will have to map
|
||||
the `/var/www/html/var/db/` folder to the docker container (see docker-compose.yaml for example).
|
||||
|
||||
You also have to create the database like described above in step 4.
|
||||
|
||||
## Running console commands
|
||||
You can run the console commands described in README by executing `docker exec --user=www-data -it partdb bin/console [command]`
|
||||
|
||||
You can run the console commands described in README by
|
||||
executing `docker exec --user=www-data -it partdb bin/console [command]`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
*Login not possible. Login page is just reloading and no error message is shown or something like "CSFR token invalid"*:
|
||||
|
||||
Clear all cookies in your browser or use a inkognito tab for Part-DB.
|
||||
This related to the fact that Part-DB can not set cookies via HTTP, after some webpage has set cookies before under localhost via https. This is a security mechanism of the browser and can not be bypassed by Part-DB.
|
||||
Clear all cookies in your browser or use an inkognito tab for Part-DB.
|
||||
This related to the fact that Part-DB can not set cookies via HTTP, after some webpage has set cookies before under
|
||||
localhost via https. This is a security mechanism of the browser and can not be bypassed by Part-DB.
|
||||
|
||||
@@ -6,25 +6,38 @@ nav_order: 4
|
||||
---
|
||||
|
||||
# Part-DB installation guide for Debian 11 (Bullseye)
|
||||
This guide shows you how to install Part-DB directly on Debian 11 using apache2 and SQLite. This guide should work with recent Ubuntu and other Debian based distributions with little to no changes.
|
||||
Depending on what you want to do, using the prebuilt docker images may be a better choice, as you dont need to install this much dependencies. See **TODO** for more information of the docker installation.
|
||||
|
||||
This guide shows you how to install Part-DB directly on Debian 11 using apache2 and SQLite. This guide should work with
|
||||
recent Ubuntu and other Debian based distributions with little to no changes.
|
||||
Depending on what you want to do, using the prebuilt docker images may be a better choice, as you don't need to install
|
||||
this many dependencies. See [here]({% link installation/installation_docker.md %}) for more information of the docker
|
||||
installation.
|
||||
|
||||
{: .warning }
|
||||
> The methods described here, configure PHP without HTTPS and therefore should only be used locally in a trusted network.
|
||||
> The methods described here, configure PHP without HTTPS and therefore should only be used locally in a trusted
|
||||
> network.
|
||||
> If you want to expose Part-DB to the internet, you HAVE to configure an SSL connection!
|
||||
|
||||
## Installation with SQLite database
|
||||
|
||||
### Install prerequisites
|
||||
|
||||
For the installation of Part-DB, we need some prerequisites. They can be installed by running the following command:
|
||||
|
||||
```bash
|
||||
sudo apt install git curl zip ca-certificates software-properties-common apt-transport-https lsb-release nano wget
|
||||
```
|
||||
|
||||
### Install PHP and apache2
|
||||
Part-DB is written in [PHP](https://php.net) and therefore needs an PHP interpreter to run. Part-DB needs PHP 8.1 or higher, however it is recommended to use the most recent version of PHP for performance reasons and future compatibility.
|
||||
|
||||
As Debian 11 does not ship PHP 8.1 in it's default repositories, we have to add a repository for it. You can skip this step if your distribution is shipping a recent version of PHP or you want to use the built-in PHP version. If you are using Debian 12, you can skip this step, as PHP 8.1 is already included in the default repositories.
|
||||
Part-DB is written in [PHP](https://php.net) and therefore needs an PHP interpreter to run. Part-DB needs PHP 8.1 or
|
||||
higher, however it is recommended to use the most recent version of PHP for performance reasons and future
|
||||
compatibility.
|
||||
|
||||
As Debian 11 does not ship PHP 8.1 in its default repositories, we have to add a repository for it. You can skip this
|
||||
step if your distribution is shipping a recent version of PHP or you want to use the built-in PHP version. If you are
|
||||
using Debian 12, you can skip this step, as PHP 8.1 is already included in the default repositories.
|
||||
|
||||
```bash
|
||||
# Add sury repository for PHP 8.1
|
||||
sudo curl -sSL https://packages.sury.org/php/README.txt | sudo bash -x
|
||||
@@ -32,14 +45,21 @@ sudo curl -sSL https://packages.sury.org/php/README.txt | sudo bash -x
|
||||
# Update package list
|
||||
sudo apt update && sudo apt upgrade
|
||||
```
|
||||
Now you can install PHP 8.1 and required packages (change the 8.1 in the package version according to the version you want to use):
|
||||
|
||||
Now you can install PHP 8.1 and required packages (change the 8.1 in the package version according to the version you
|
||||
want to use):
|
||||
|
||||
```bash
|
||||
sudo apt install php8.1 libapache2-mod-php8.1 php8.1-opcache php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-bcmath php8.1-intl php8.1-zip php8.1-xsl php8.1-sqlite3 php8.1-mysql
|
||||
```
|
||||
|
||||
The apache2 webserver should be already installed with this command and configured basically.
|
||||
|
||||
### Install composer
|
||||
Part-DB uses [composer](https://getcomposer.org/) to install required PHP libraries. As the versions shipped in the repositories is pretty old we install it manually:
|
||||
|
||||
Part-DB uses [composer](https://getcomposer.org/) to install required PHP libraries. As the versions shipped in the
|
||||
repositories is pretty old we install it manually:
|
||||
|
||||
```bash
|
||||
# Download composer installer script
|
||||
wget -O /tmp/composer-setup.php https://getcomposer.org/installer
|
||||
@@ -50,7 +70,10 @@ chmod +x /usr/local/bin/composer
|
||||
```
|
||||
|
||||
### Install yarn and nodejs
|
||||
To build the frontend (the user interface) Part-DB uses [yarn](https://yarnpkg.com/). As it dependens on nodejs and the shipped versions are pretty old, we install new versions from offical nodejs repository:
|
||||
|
||||
To build the frontend (the user interface) Part-DB uses [yarn](https://yarnpkg.com/). As it depends on Node.js and the
|
||||
shipped versions are pretty old, we install new versions from official Node.js repository:
|
||||
|
||||
```bash
|
||||
# Add recent node repository (nodejs 18 is supported until 2025)
|
||||
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||
@@ -59,6 +82,7 @@ sudo apt install nodejs
|
||||
```
|
||||
|
||||
We can install yarn with the following commands:
|
||||
|
||||
```bash
|
||||
# Add yarn repository
|
||||
curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
|
||||
@@ -68,46 +92,64 @@ sudo apt update && sudo apt install yarn
|
||||
```
|
||||
|
||||
### Create a folder for Part-DB and download it
|
||||
We now have all prerequisites installed and can start to install Part-DB. We will create a folder for Part-DB in a webfolder of apache2 and download it to this folder. The downloading is done via git, which allows you to update easily later.
|
||||
|
||||
We now have all prerequisites installed and can start to install Part-DB. We will create a folder for Part-DB in the
|
||||
webroot of apache2 and download it to this folder. The downloading is done via git, which allows you to update easily
|
||||
later.
|
||||
|
||||
```bash
|
||||
# Download Part-DB into the new folder /var/www/partdb
|
||||
git clone https://github.com/Part-DB/Part-DB-symfony.git /var/www/partdb
|
||||
```
|
||||
|
||||
By default you are now on the latest development version. In most cases you want to use the latest stable version. You can switch to the latest stable version (tagged) by running the following command:
|
||||
By default, you are now on the latest development version. In most cases you want to use the latest stable version. You
|
||||
can switch to the latest stable version (tagged) by running the following command:
|
||||
|
||||
```bash
|
||||
# This finds the latest release/tag and checks it out
|
||||
git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
|
||||
```
|
||||
Alternatively you can checkout a specific version by running (see [GitHub Relases page](https://github.com/Part-DB/Part-DB-server/releases) for a list of available versions):
|
||||
|
||||
Alternatively you can check out a specific version by running (
|
||||
see [GitHub Releases page](https://github.com/Part-DB/Part-DB-server/releases) for a list of available versions):
|
||||
|
||||
```bash
|
||||
# This checks out the version 1.5.2
|
||||
git checkout v1.5.2
|
||||
```
|
||||
|
||||
Change ownership of the files to the apache user:
|
||||
|
||||
```bash
|
||||
chown -R www-data:www-data /var/www/partdb
|
||||
```
|
||||
|
||||
For the next steps we should be in the Part-DB folder, so move into it:
|
||||
|
||||
```bash
|
||||
cd /var/www/partdb
|
||||
```
|
||||
|
||||
### Create configuration for Part-DB
|
||||
The basic configuration of Part-DB is done by a `.env.local` file in the main directory. Create on by from the default configuration:
|
||||
|
||||
The basic configuration of Part-DB is done by a `.env.local` file in the main directory. Create on by from the default
|
||||
configuration:
|
||||
|
||||
```bash
|
||||
cp .env .env.local
|
||||
```
|
||||
|
||||
In your `.env.local` you can configure Part-DB according to your wishes. A full list of configuration options can be found [here](../configuration.md).
|
||||
In your `.env.local` you can configure Part-DB according to your wishes. A full list of configuration options can be
|
||||
found [here](../configuration.md).
|
||||
Other configuration options like the default language or default currency can be found in `config/parameters.yaml`.
|
||||
|
||||
Please check that the `partdb.default_currency` value in `config/parameters.yaml` matches your mainly used currency, as this can not be changed after creating price informations.
|
||||
Please check that the `partdb.default_currency` value in `config/parameters.yaml` matches your mainly used currency, as
|
||||
this can not be changed after creating price information.
|
||||
|
||||
### Install dependencies for Part-DB and build frontend
|
||||
|
||||
Part-DB depends on several other libraries and components. Install them by running the following commands:
|
||||
|
||||
```bash
|
||||
# Install composer dependencies (please note the sudo command, to run it under the web server user)
|
||||
sudo -u www-data composer install --no-dev -o
|
||||
@@ -121,32 +163,48 @@ sudo yarn build
|
||||
### Clear cache
|
||||
|
||||
To ensure everything is working, clear the cache:
|
||||
|
||||
```bash
|
||||
sudo -u www-data php bin/console cache:clear
|
||||
```
|
||||
|
||||
### Check if everything is installed
|
||||
|
||||
To check if everything is installed, run the following command:
|
||||
|
||||
```bash
|
||||
sudo -u www-data php bin/console partdb:check-requirements
|
||||
```
|
||||
The most things should be green, and no red ones. Yellow messages means optional dependencies which are not important but can improve performance and functionality.
|
||||
|
||||
The most things should be green, and no red ones. Yellow messages means optional dependencies which are not important
|
||||
but can improve performance and functionality.
|
||||
|
||||
### Create a database for Part-DB
|
||||
Part-DB by default uses a file based sqlite database to store the data. Use the following command to create the database. The database will normally created at `/var/www/partdb/var/app.db`.
|
||||
|
||||
Part-DB by default uses a file based sqlite database to store the data. Use the following command to create the
|
||||
database. The database will normally be created at `/var/www/partdb/var/app.db`.
|
||||
|
||||
```bash
|
||||
sudo -u www-data php bin/console doctrine:migrations:migrate
|
||||
```
|
||||
|
||||
The command will warn you about schema changes and potential data loss. Continue with typing `yes`.
|
||||
|
||||
The command will output several lines of informations. Somewhere should be a a yellow background message like `The initial password for the "admin" user is: f502481134`. Write down this password as you will need it later for inital login.
|
||||
The command will output several lines of information. Somewhere should be a yellow background message
|
||||
like `The initial password for the "admin" user is: f502481134`. Write down this password as you will need it later for
|
||||
initial login.
|
||||
|
||||
### Configure apache2 to show Part-DB
|
||||
Part-DB is now configured, but we have to say apache2 to serve Part-DB as web application. This is done by creating a new apache site:
|
||||
|
||||
Part-DB is now configured, but we have to say apache2 to serve Part-DB as web application. This is done by creating a
|
||||
new apache site:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/apache2/sites-available/partdb.conf
|
||||
```
|
||||
|
||||
and add the following content (change ServerName and ServerAlias to your needs):
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName partdb.lan
|
||||
@@ -163,33 +221,44 @@ and add the following content (change ServerName and ServerAlias to your needs):
|
||||
CustomLog /var/log/apache2/partdb_access.log combined
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
Activate the new site by:
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/apache2/sites-available/partdb.conf /etc/apache2/sites-enabled/partdb.conf
|
||||
```
|
||||
|
||||
Configure apache to show pretty URL pathes for Part-DB (`/label/dialog` instead of `/index.php/label/dialog`):
|
||||
Configure apache to show pretty URL paths for Part-DB (`/label/dialog` instead of `/index.php/label/dialog`):
|
||||
|
||||
```bash
|
||||
sudo a2enmod rewrite
|
||||
```
|
||||
|
||||
If you want to access Part-DB via the IP-Address of the server, instead of the domain name, you have to remove the apache2 default configuration with:
|
||||
If you want to access Part-DB via the IP-Address of the server, instead of the domain name, you have to remove the
|
||||
apache2 default configuration with:
|
||||
|
||||
```bash
|
||||
sudo rm /etc/apache2/sites-enabled/000-default.conf
|
||||
```
|
||||
|
||||
Restart the apache2 webserver with:
|
||||
|
||||
```bash
|
||||
sudo service apache2 restart
|
||||
```
|
||||
|
||||
and Part-DB should now be available under `http://YourServerIP` (or `http://partdb.lan` if you configured DNS in your network to point on the server).
|
||||
and Part-DB should now be available under `http://YourServerIP` (or `http://partdb.lan` if you configured DNS in your
|
||||
network to point on the server).
|
||||
|
||||
### Login to Part-DB
|
||||
Navigate to the Part-DB web interface and login via the user icon in the top right corner. You can login using the username `admin` and the password you have written down earlier.
|
||||
|
||||
Navigate to the Part-DB web interface and login via the user icon in the top right corner. You can log in using the
|
||||
username `admin` and the password you have written down earlier.
|
||||
|
||||
## Update Part-DB
|
||||
|
||||
If you want to update your existing Part-DB installation, you just have to run the following commands:
|
||||
|
||||
```bash
|
||||
# Move into Part-DB folder
|
||||
cd /var/www/partdb
|
||||
@@ -218,6 +287,7 @@ sudo -u www-data php bin/console cache:clear
|
||||
```
|
||||
|
||||
## MySQL/MariaDB database
|
||||
|
||||
To use a MySQL database, follow the steps from above (except the creation of database, we will do this later).
|
||||
Debian 11 does not ship MySQL in its repositories anymore, so we use the compatible MariaDB instead:
|
||||
|
||||
@@ -228,9 +298,11 @@ sudo apt update && sudo apt install mariadb-server
|
||||
```
|
||||
|
||||
2. Configure maria-db with:
|
||||
|
||||
```bash
|
||||
sudo mysql_secure_installation
|
||||
```
|
||||
|
||||
When asked for the root password, just press enter, as we have not set a root password yet.
|
||||
In the next steps you are asked if you want to switch to unix_socket authentication, answer with `n` and press enter.
|
||||
Then you are asked if you want to remove anonymous users, answer with `y` and press enter.
|
||||
@@ -239,33 +311,42 @@ Then you are asked if you want to remove the test database and access to it, ans
|
||||
Then you are asked if you want to reload the privilege tables now, answer with `y` and press enter.
|
||||
|
||||
3. Create a new database and user for Part-DB: Run the following commands:
|
||||
|
||||
```bash
|
||||
sudo mariadb
|
||||
```
|
||||
|
||||
A SQL shell will open, in which you can run the following commands to create a new database and user for Part-DB.
|
||||
Replace 'YOUR_SECRET_PASSWORD' with a secure password.
|
||||
|
||||
```sql
|
||||
CREATE DATABASE partdb;
|
||||
GRANT ALL PRIVILEGES ON partdb.* TO 'partdb'@'localhost' IDENTIFIED BY 'YOUR_SECRET_PASSWORD';
|
||||
```
|
||||
Finally save the changes with:
|
||||
|
||||
Finally, save the changes with:
|
||||
|
||||
```sql
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
and exit the SQL shell with:
|
||||
|
||||
```sql
|
||||
exit
|
||||
```
|
||||
|
||||
4. Configure Part-DB to use the new database. Open your `.env.local` file and search the line `DATABASE_URL`.
|
||||
Change it to the following (you have to replace `YOUR_SECRET_PASSWORD` with the password you have choosen in step 3):
|
||||
Change it to the following (you have to replace `YOUR_SECRET_PASSWORD` with the password you have chosen in step 3):
|
||||
|
||||
```
|
||||
DATABASE_URL=DATABASE_URL=mysql://partdb:YOUR_SECRET_PASSWORD@127.0.0.1:3306/partdb
|
||||
```
|
||||
|
||||
5. Create the database schema with:
|
||||
|
||||
```bash
|
||||
sudo -u www-data php bin/console doctrine:migrations:migrate
|
||||
```
|
||||
|
||||
6. The migration step should have shown you a password for the admin user, which you can use now to login to Part-DB.
|
||||
6. The migration step should have shown you a password for the admin user, which you can use now to log in to Part-DB.
|
||||
|
||||
@@ -6,14 +6,18 @@ nav_order: 10
|
||||
---
|
||||
|
||||
# Nginx
|
||||
|
||||
You can also use [nginx](https://www.nginx.com/) as webserver for Part-DB. Setup Part-DB with apache is a bit easier, so
|
||||
this is the method shown in the guides. This guide assumes that you already have a working nginx installation with PHP
|
||||
configured.
|
||||
|
||||
## Setup
|
||||
1. Install composer and yarn like described in the [apache guide]({% link installation/installation_guide-debian.md %}#install-composer).
|
||||
|
||||
1. Install composer and yarn like described in the [apache guide]({% link installation/installation_guide-debian.md
|
||||
%}#install-composer).
|
||||
2. Create a folder for Part-DB and install and configure it as described
|
||||
3. Instead of creating the config for apache, add the following snippet to your nginx config:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
# Redirect all HTTP requests to HTTPS
|
||||
@@ -64,4 +68,6 @@ server {
|
||||
ssl_prefer_server_ciphers on;
|
||||
}
|
||||
```
|
||||
4. Restart nginx with `sudo systemctl restart nginx` and you should be able to access Part-DB under your configured domain.
|
||||
|
||||
4. Restart nginx with `sudo systemctl restart nginx` and you should be able to access Part-DB under your configured
|
||||
domain.
|
||||
@@ -9,13 +9,32 @@ nav_order: 11
|
||||
|
||||
If you want to put Part-DB behind a reverse proxy, you have to configure Part-DB correctly to make it work properly.
|
||||
|
||||
You have to set the `TRUSTED_PROXIES` environment variable to the IP address of your reverse proxy
|
||||
(either in your `docker-compose.yaml` in the case of docker, or `.env.local` in case of direct installation).
|
||||
You have to set the `TRUSTED_PROXIES` environment variable to the IP address of your reverse proxy
|
||||
(either in your `docker-compose.yaml` in the case of docker, or `.env.local` in case of direct installation).
|
||||
If you have multiple reverse proxies, you can set multiple IP addresses separated by a comma (or specify a range).
|
||||
|
||||
For example, if your reverse proxy has the IP address `192.168.2.10`, your value should be:
|
||||
For example, if your reverse proxy has the IP address `192.168.2.10`, your value should be:
|
||||
|
||||
```
|
||||
TRUSTED_PROXIES=192.168.2.10
|
||||
```
|
||||
|
||||
Set the `DEFAULT_URI` environment variable to the URL of your Part-DB installation, available from the outside (so via the reverse proxy).
|
||||
Set the `DEFAULT_URI` environment variable to the URL of your Part-DB installation, available from the outside (so via
|
||||
the reverse proxy).
|
||||
|
||||
## Part-DB in a subpath via reverse proxy
|
||||
|
||||
If you put Part-DB into a subpath via the reverse proxy, you have to configure your webserver to include `X-Forwarded-Prefix` in the request headers.
|
||||
For example if you put Part-DB behind a reverse proxy with the URL `https://example.com/partdb`, you have to set the `X-Forwarded-Prefix` header to `/partdb`.
|
||||
|
||||
In apache, you can do this by adding the following line to your virtual host configuration:
|
||||
|
||||
```
|
||||
RequestHeader set X-Forwarded-Prefix "/partdb"
|
||||
```
|
||||
|
||||
and in nginx, you can do this by adding the following line to your server configuration:
|
||||
|
||||
```
|
||||
proxy_set_header X-Forwarded-Prefix "/partdb";
|
||||
```
|
||||
@@ -7,42 +7,55 @@ nav_order: 12
|
||||
|
||||
# Single Sign-On via SAML
|
||||
|
||||
Part-DB supports Single Sign-On via SAML. This means that you can use your existing SAML identity provider to log in to Part-DB.
|
||||
Using an intermediate SAML server like [Keycloak](https://www.keycloak.org/), also allows you to connect Part-DB to a LDAP or Active Directory server.
|
||||
Part-DB supports Single Sign-On via SAML. This means that you can use your existing SAML identity provider to log in to
|
||||
Part-DB.
|
||||
Using an intermediate SAML server like [Keycloak](https://www.keycloak.org/), also allows you to connect Part-DB to an
|
||||
LDAP or Active Directory server.
|
||||
|
||||
{: .important }
|
||||
> This feature is for advanced users only. Single Sign-On is useful for large organizations with many users, which are already using SAML for other services.
|
||||
> This feature is for advanced users only. Single Sign-On is useful for large organizations with many users, which are
|
||||
> already using SAML for other services.
|
||||
> If you have only one or a few users, you should use the built-in authentication system of Part-DB.
|
||||
> This guide assumes that you already have an SAML identity provider set up and working, and have a basic understanding of how SAML works.
|
||||
> This guide assumes that you already have an SAML identity provider set up and working, and have a basic understanding
|
||||
> of how SAML works.
|
||||
|
||||
{: .warning }
|
||||
> This feature is currently in beta. Please report any bugs you find.
|
||||
> So far it has only tested with Keycloak, but it should work with any SAML 2.0 compatible identity provider.
|
||||
|
||||
This guide will show you how to configure Part-DB with [Keycloak](https://www.keycloak.org/) as the SAML identity provider,
|
||||
but it should work with any SAML 2.0 compatible identity provider.
|
||||
This guide will show you how to configure Part-DB with [Keycloak](https://www.keycloak.org/) as the SAML identity
|
||||
provider, but it should work with any SAML 2.0 compatible identity provider.
|
||||
|
||||
This guide assumes that you have a working Keycloak installation with some users. If you don't, you can follow the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/getting_started/index.html).
|
||||
This guide assumes that you have a working Keycloak installation with some users. If you don't, you can follow
|
||||
the [Keycloak Getting Started Guide](https://www.keycloak.org/docs/latest/getting_started/index.html).
|
||||
|
||||
{: .important }
|
||||
> Part-DB associates local users with SAML users by their username. That means if the username of a SAML user changes, a new local user will be created (and the old account can not be accessed).
|
||||
> You should make sure that the username of a SAML user does not change. If you use Keycloak make sure that the possibility to change the username is disabled (which is by default).
|
||||
> If you really have to rename a SAML user, a Part-DB admin can rename the local user in the Part-DB in the admin panel, to match the new username of the SAML user.
|
||||
> Part-DB associates local users with SAML users by their username. That means if the username of a SAML user changes, a
|
||||
> new local user will be created (and the old account can not be accessed).
|
||||
> You should make sure that the username of a SAML user does not change. If you use Keycloak make sure that the
|
||||
> possibility to change the username is disabled (which is by default).
|
||||
> If you really have to rename a SAML user, a Part-DB admin can rename the local user in the Part-DB in the admin panel,
|
||||
> to match the new username of the SAML user.
|
||||
|
||||
## Configure basic SAML connection
|
||||
|
||||
### Create a new SAML client
|
||||
1. First, you need to configure a new SAML client in Keycloak. Login in to your Keycloak admin console and go to the `Clients` page.
|
||||
2. Click on `Create client` and select `SAML` as type from the dropdown menu. For the client ID, you can use anything you want, but it should be unique.
|
||||
*It is recommended to set this value to the domain name of your Part-DB installation, with an attached `/sp` (e.g. `https://partdb.yourdomain.invalid/sp`)*.
|
||||
The name field should be set to something human-readable, like `Part-DB`.
|
||||
|
||||
1. First, you need to configure a new SAML client in Keycloak. Login in to your Keycloak admin console and go to
|
||||
the `Clients` page.
|
||||
2. Click on `Create client` and select `SAML` as type from the dropdown menu. For the client ID, you can use anything
|
||||
you want, but it should be unique.
|
||||
*It is recommended to set this value to the domain name of your Part-DB installation, with an attached `/sp` (
|
||||
e.g. `https://partdb.yourdomain.invalid/sp`)*.
|
||||
The name field should be set to something human-readable, like `Part-DB`.
|
||||
3. Click on `Save` to create the new client.
|
||||
|
||||
### Configure the SAML client
|
||||
|
||||
1. Now you need to configure the SAML client. Go to the `Settings` tab and set the following values:
|
||||
* Set `Home URL` to the homepage of your Part-DB installation (e.g. `https://partdb.yourdomain.invalid/`).
|
||||
* Set `Valid redirect URIs` to your homepage with a wildcard at the end (e.g. `https://partdb.yourdomain.invalid/*`).
|
||||
* Set `Valid redirect URIs` to your homepage with a wildcard at the end (
|
||||
e.g. `https://partdb.yourdomain.invalid/*`).
|
||||
* Set `Valid post logout redirect URIs` to `+` to allow all urls from the `Valid redirect URIs`.
|
||||
* Set `Name ID format` to `username`
|
||||
* Ensure `Force POST binding` is enabled.
|
||||
@@ -52,30 +65,47 @@ The name field should be set to something human-readable, like `Part-DB`.
|
||||
|
||||
Click on `Save` to save the changes.
|
||||
2. Go to the `Advanced` tab and set the following values:
|
||||
* Assertion Consumer Service POST Binding URL to your homepage with `/saml/acs` at the end (e.g. `https://partdb.yourdomain.invalid/saml/acs`).
|
||||
* Logout Service POST Binding URL to your homepage with `/logout` at the end (e.g. `https://partdb.yourdomain.invalid/logout`).
|
||||
* Assertion Consumer Service POST Binding URL to your homepage with `/saml/acs` at the end (
|
||||
e.g. `https://partdb.yourdomain.invalid/saml/acs`).
|
||||
* Logout Service POST Binding URL to your homepage with `/logout` at the end (
|
||||
e.g. `https://partdb.yourdomain.invalid/logout`).
|
||||
3. Go to Keys tab and ensure `Client Signature Required` is enabled.
|
||||
4. In the Keys tab click on `Generate new keys`. This will generate a new key pair for the SAML client. The private key will be downloaded to your computer.
|
||||
4. In the Keys tab click on `Generate new keys`. This will generate a new key pair for the SAML client. The private key
|
||||
will be downloaded to your computer.
|
||||
|
||||
### Configure Part-DB to use SAML
|
||||
|
||||
1. Open the `.env.local` file of Part-DB (or the docker-compose.yaml) for edit
|
||||
2. Set the `SAMLP_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the previous step. It should start with `MIEE` and end with `=`.
|
||||
3. Set the `SAML_SP_X509_CERT` environment variable to the content of the Certificate field shown in the `Keys` tab of the SAML client in Keycloak. It should start with `MIIC` and end with `=`.
|
||||
4. Set the `SAML_SP_ENTITY_ID` environment variable to the entityID of the SAML client in Keycloak (e.g. `https://partdb.yourdomain.invalid/sp`).
|
||||
5. In Keycloak navigate to `Realm Settings` -> `SAML 2.0 Identity Provider` (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/descriptor) to show the SAML metadata.
|
||||
6. Copy the `entityID` value from the metadata to the `SAML_IDP_ENTITY_ID` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master`).
|
||||
7. Copy the `Single Sign-On Service` value from the metadata to the `SAML_IDP_SINGLE_SIGN_ON_SERVICE` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml`).
|
||||
8. Copy the `Single Logout Service` value from the metadata to the `SAML_IDP_SINGLE_LOGOUT_SERVICE` configuration variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/logout`).
|
||||
9. Copy the `X.509 Certificate` value from the metadata to the `SAML_IDP_X509_CERT` configuration variable of Part-DB (it should start with `MIIC` and should be pretty long).
|
||||
10. Set the `DEFAULT_URI` to the homepage (on the publicly available domain) of your Part-DB installation (e.g. `https://partdb.yourdomain.invalid/`). It must end with a slash.
|
||||
2. Set the `SAMLP_SP_PRIVATE_KEY` environment variable to the content of the private key file you downloaded in the
|
||||
previous step. It should start with `MIEE` and end with `=`.
|
||||
3. Set the `SAML_SP_X509_CERT` environment variable to the content of the Certificate field shown in the `Keys` tab of
|
||||
the SAML client in Keycloak. It should start with `MIIC` and end with `=`.
|
||||
4. Set the `SAML_SP_ENTITY_ID` environment variable to the entityID of the SAML client in Keycloak (
|
||||
e.g. `https://partdb.yourdomain.invalid/sp`).
|
||||
5. In Keycloak navigate to `Realm Settings` -> `SAML 2.0 Identity Provider` (by default something
|
||||
like `https://idp.yourdomain.invalid/realms/master/protocol/saml/descriptor) to show the SAML metadata.
|
||||
6. Copy the `entityID` value from the metadata to the `SAML_IDP_ENTITY_ID` configuration variable of Part-DB (by default
|
||||
something like `https://idp.yourdomain.invalid/realms/master`).
|
||||
7. Copy the `Single Sign-On Service` value from the metadata to the `SAML_IDP_SINGLE_SIGN_ON_SERVICE` configuration
|
||||
variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml`).
|
||||
8. Copy the `Single Logout Service` value from the metadata to the `SAML_IDP_SINGLE_LOGOUT_SERVICE` configuration
|
||||
variable of Part-DB (by default something like `https://idp.yourdomain.invalid/realms/master/protocol/saml/logout`).
|
||||
9. Copy the `X.509 Certificate` value from the metadata to the `SAML_IDP_X509_CERT` configuration variable of Part-DB (
|
||||
it should start with `MIIC` and should be pretty long).
|
||||
10. Set the `DEFAULT_URI` to the homepage (on the publicly available domain) of your Part-DB installation (
|
||||
e.g. `https://partdb.yourdomain.invalid/`). It must end with a slash.
|
||||
11. Set the `SAML_ENABLED` configuration in Part-DB to 1 to enable SAML authentication.
|
||||
|
||||
When you access the Part-DB login form now, you should see a new button to log in via SSO. Click on it to be redirected to the SAML identity provider and log in.
|
||||
When you access the Part-DB login form now, you should see a new button to log in via SSO. Click on it to be redirected
|
||||
to the SAML identity provider and log in.
|
||||
|
||||
In the following sections, you will learn how to configure that Part-DB uses the data provided by the SAML identity provider to fill out user informations.
|
||||
In the following sections, you will learn how to configure that Part-DB uses the data provided by the SAML identity
|
||||
provider to fill out user information.
|
||||
|
||||
### Set user information based on SAML attributes
|
||||
Part-DB can set basic user information like the username, the real name and the email address based on the SAML attributes provided by the SAML identity provider.
|
||||
|
||||
Part-DB can set basic user information like the username, the real name and the email address based on the SAML
|
||||
attributes provided by the SAML identity provider.
|
||||
To do this, you need to configure your SAML identity provider to provide the following attributes:
|
||||
|
||||
* `email` or `urn:oid:1.2.840.113549.1.9.1` for the email address
|
||||
@@ -83,68 +113,125 @@ To do this, you need to configure your SAML identity provider to provide the fol
|
||||
* `lastName` or `urn:oid:2.5.4.4` for the last name
|
||||
* `department` for the department field of the user
|
||||
|
||||
You can omit any of these attributes, but then the corresponding field will be empty (but can be overriden by an administrator).
|
||||
These values are written to Part-DB database, whenever the user logs in via SAML (the user is created on the first login, and updated on every login).
|
||||
You can omit any of these attributes, but then the corresponding field will be empty (but can be overwritten by an
|
||||
administrator).
|
||||
These values are written to Part-DB database, whenever the user logs in via SAML (the user is created on the first
|
||||
login, and updated on every login).
|
||||
|
||||
To configure Keycloak to provide these attributes, you need to go to the `Client scopes` page and select the `sp-dedicatd` client scope (or create a new one).
|
||||
To configure Keycloak to provide these attributes, you need to go to the `Client scopes` page and select
|
||||
the `sp-dedicatd` client scope (or create a new one).
|
||||
In the scope configuration page, click on `Add mappers` and `From predefined mappers`. Select the following mappers:
|
||||
|
||||
* `X500 email`
|
||||
* `X500 givenName`
|
||||
* `X500 surname`
|
||||
|
||||
and click `Add`. Now Part-DB will be provided with the email, first name and last name of the user based on the Keycloak user database.
|
||||
and click `Add`. Now Part-DB will be provided with the email, first name and last name of the user based on the Keycloak
|
||||
user database.
|
||||
|
||||
### Configure permissions for SAML users
|
||||
On the first login of a SAML user, Part-DB will create a new user in the database. This user will have the same username as the SAML user, but no password set. The user will be marked as a SAML user, so he can only login via SAML in the future. However in other aspects the user is a normal user, so Part-DB admins can set permissions for SAML users like for any other user and override permissions assigned via groups.
|
||||
|
||||
However for large organizations you maybe want to automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For this purpose Part-DB allows you to map SAML roles or groups to Part-DB groups. See the next section for details.
|
||||
On the first login of a SAML user, Part-DB will create a new user in the database. This user will have the same username
|
||||
as the SAML user, but no password set. The user will be marked as a SAML user, so he can only log in via SAML in the
|
||||
future. However, in other aspects the user is a normal user, so Part-DB admins can set permissions for SAML users like
|
||||
for any other user and override permissions assigned via groups.
|
||||
|
||||
For large organizations you maybe want to automatically assign permissions to SAML users based on the roles or
|
||||
groups configured in the identity provider. For this purpose Part-DB allows you to map SAML roles or groups to Part-DB
|
||||
groups. See the next section for details.
|
||||
|
||||
### Map SAML roles to Part-DB groups
|
||||
Part-DB allows you to configure a mapping between SAML roles or groups and Part-DB groups. This allows you to automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For example if a user at your SAML provider has the role `admin`, you can configure Part-DB to assign the `admin` group to this user. This will give the user all permissions of the `admin` group.
|
||||
|
||||
For this you need first have to create the groups in Part-DB, to which you want to assign the users and configure their permissions. You will need the IDs of the groups, which you can find in the `System->Group` page of Part-DB in the Info tab.
|
||||
Part-DB allows you to configure a mapping between SAML roles or groups and Part-DB groups. This allows you to
|
||||
automatically assign permissions to SAML users based on the roles or groups configured in the identity provider. For
|
||||
example if a user at your SAML provider has the role `admin`, you can configure Part-DB to assign the `admin` group to
|
||||
this user. This will give the user all permissions of the `admin` group.
|
||||
|
||||
The map is provided as [JSON](https://en.wikipedia.org/wiki/JSON) encoded map between the SAML role and the group ID, which has the form `{"saml_role": group_id, "*": group_id, ...}`. You can use the `*` key to assign a group to all users, which are not in any other group. The map is configured via the `SAML_ROLE_MAPPING` environment variable, which you can configure via the `.env.local` or `docker-compose.yml` file. Please note that you have to enclose the JSON string in single quotes here, as JSON itself uses double quotes (e.g. `SAML_ROLE_MAPPING='{ "*": 2, "editor": 3, "admin": 1 }`).
|
||||
For this you need first have to create the groups in Part-DB, to which you want to assign the users and configure their
|
||||
permissions. You will need the IDs of the groups, which you can find in the `System->Group` page of Part-DB in the Info
|
||||
tab.
|
||||
|
||||
For example if you want to assign the group with ID 1 (by default admin) to every SAML user which has the role `admin`, the role with ID 3 (by default editor) to every SAML user with the role `editor` and everybody else to the group with ID 2 (by default readonly), you can configure the following map:
|
||||
The map is provided as [JSON](https://en.wikipedia.org/wiki/JSON) encoded map between the SAML role and the group ID,
|
||||
which has the form `{"saml_role": group_id, "*": group_id, ...}`. You can use the `*` key to assign a group to all
|
||||
users, which are not in any other group. The map is configured via the `SAML_ROLE_MAPPING` environment variable, which
|
||||
you can configure via the `.env.local` or `docker-compose.yml` file. Please note that you have to enclose the JSON
|
||||
string in single quotes here, as JSON itself uses double quotes (
|
||||
e.g. `SAML_ROLE_MAPPING='{ "*": 2, "editor": 3, "admin": 1 }`).
|
||||
|
||||
For example if you want to assign the group with ID 1 (by default admin) to every SAML user which has the role `admin`,
|
||||
the role with ID 3 (by default editor) to every SAML user with the role `editor` and everybody else to the group with ID
|
||||
2 (by default readonly), you can configure the following map:
|
||||
|
||||
```
|
||||
SAML_ROLE_MAPPING='{"admin": 1, "editor": 3, "*": 2}'
|
||||
```
|
||||
|
||||
Please not that the order of the mapping is important. The first matching role will be assigned to the user. So if you have a user with the roles `admin` and `editor`, he will be assigned to the group with ID 1 (admin) and not to the group with ID 3 (editor), as the `admin` role comes first in the JSON map.
|
||||
This mean that you should always put the most specific roles (e.g. admins) first of the map and the most general roles (e.g. normal users) later.
|
||||
Please note that the order of the mapping is important. The first matching role will be assigned to the user. So if you
|
||||
have a user with the roles `admin` and `editor`, he will be assigned to the group with ID 1 (admin) and not to the group
|
||||
with ID 3 (editor), as the `admin` role comes first in the JSON map.
|
||||
This mean that you should always put the most specific roles (e.g. admins) first of the map and the most general roles (
|
||||
e.g. normal users) later.
|
||||
|
||||
If you want to assign users with a certain role to a empty group, provide the group ID -1 as the value. This is not a valid group ID, so the user will not be assigned to any group.
|
||||
If you want to assign users with a certain role to an empty group, provide the group ID -1 as the value. This is not a
|
||||
valid group ID, so the user will not be assigned to any group.
|
||||
|
||||
The SAML roles (or groups depending on your configuration), have to be supplied via a SAML attribute `group`. You have to configure your SAML identity provider to provide this attribute. For example in Keycloak you can configure this attribute in the `Client scopes` page. Select the `sp-dedicated` client scope (or create a new one) and click on `Add mappers`. Select `Role mapping` or `Group membership`, change the field name and click `Add`. Now Part-DB will be provided with the groups of the user based on the Keycloak user database.
|
||||
The SAML roles (or groups depending on your configuration), have to be supplied via a SAML attribute `group`. You have
|
||||
to configure your SAML identity provider to provide this attribute. For example in Keycloak you can configure this
|
||||
attribute in the `Client scopes` page. Select the `sp-dedicated` client scope (or create a new one) and click
|
||||
on `Add mappers`. Select `Role mapping` or `Group membership`, change the field name and click `Add`. Now Part-DB will
|
||||
be provided with the groups of the user based on the Keycloak user database.
|
||||
|
||||
By default, the group is assigned to the user on the first login and updated on every login based on the SAML attributes. This allows you to configure the groups in the SAML identity provider and the users will automatically stay up to date with their permissions. However, if you want to disable this behavior (and let the Part-DB admins configure the groups manually, after the first login), you can set the `SAML_UPDATE_GROUP_ON_LOGIN` environment variable to `false`. If you want to disable the automatic group assignment completly (so not even on the first login of a user), set the `SAML_ROLE_MAPPING` to `{}` (empty JSON object).
|
||||
By default, the group is assigned to the user on the first login and updated on every login based on the SAML
|
||||
attributes. This allows you to configure the groups in the SAML identity provider and the users will automatically stay
|
||||
up to date with their permissions. However, if you want to disable this behavior (and let the Part-DB admins configure
|
||||
the groups manually, after the first login), you can set the `SAML_UPDATE_GROUP_ON_LOGIN` environment variable
|
||||
to `false`. If you want to disable the automatic group assignment completely (so not even on the first login of a user),
|
||||
set the `SAML_ROLE_MAPPING` to `{}` (empty JSON object).
|
||||
|
||||
## Overview of possible SAML attributes used by Part-DB
|
||||
The following table shows all SAML attributes, which can be usedby Part-DB. If your identity provider is configured to provide these attributes, you can use to automatically fill the corresponding fields of the user in Part-DB.
|
||||
|
||||
The following table shows all SAML attributes, which can be used by Part-DB. If your identity provider is configured to
|
||||
provide these attributes, you can use to automatically fill the corresponding fields of the user in Part-DB.
|
||||
|
||||
| SAML attribute | Part-DB user field | Description |
|
||||
|-------------------------------------------|-------------------|-------------------------------------------------------------------|
|
||||
| `urn:oid:1.2.840.113549.1.9.1` or `email` | email | The email address of the user. |
|
||||
| `urn:oid:2.5.4.42` or `firstName` | firstName | The first name of the user. |
|
||||
| `urn:oid:2.5.4.4` or `lastName` | lastName | The last name of the user. |
|
||||
| `department` | department | The department of the user. |
|
||||
| `group` | group | The group of the user (determined by `SAML_ROLE_MAPPING` option). |
|
||||
|-------------------------------------------|--------------------|-------------------------------------------------------------------|
|
||||
| `urn:oid:1.2.840.113549.1.9.1` or `email` | email | The email address of the user. |
|
||||
| `urn:oid:2.5.4.42` or `firstName` | firstName | The first name of the user. |
|
||||
| `urn:oid:2.5.4.4` or `lastName` | lastName | The last name of the user. |
|
||||
| `department` | department | The department of the user. |
|
||||
| `group` | group | The group of the user (determined by `SAML_ROLE_MAPPING` option). |
|
||||
|
||||
## Use SAML Login for existing users
|
||||
Part-DB distinguishes between local users and SAML users. Local users are users, which can login via Part-DB login form and which use the password (hash) saved in the Part-DB database. SAML users are stored in the database too (they are created on the first login of the user via SAML), but they use the SAML identity provider to authenticate the user and have no password stored in the database. When you try you will get an error message.
|
||||
|
||||
For security reasons it is not possible to authenticate via SAML as a local user (and vice versa). So if you have existing users in your Part-DB database and want them to be able to login via SAML in the future, you can use the `php bin/console partdb:user:convert-to-saml-user username` command to convert them to SAML users. This will remove the password hash from the database and mark them as SAML users, so they can login via SAML in the future.
|
||||
Part-DB distinguishes between local users and SAML users. Local users are users, which can log in via Part-DB login form
|
||||
and which use the password (hash) saved in the Part-DB database. SAML users are stored in the database too (they are
|
||||
created on the first login of the user via SAML), but they use the SAML identity provider to authenticate the user and
|
||||
have no password stored in the database. When you try you will get an error message.
|
||||
|
||||
The reverse is also possible: If you have existing SAML users and want them to be able to login via the Part-DB login form, you can use the `php bin/console partdb:user:convert-to-saml-user --to-local username` command to convert them to local users. You have to set an password for the user afterwards.
|
||||
For security reasons it is not possible to authenticate via SAML as a local user (and vice versa). So if you have
|
||||
existing users in your Part-DB database and want them to be able to log in via SAML in the future, you can use
|
||||
the `php bin/console partdb:user:convert-to-saml-user username` command to convert them to SAML users. This will remove
|
||||
the password hash from the database and mark them as SAML users, so they can log in via SAML in the future.
|
||||
|
||||
The reverse is also possible: If you have existing SAML users and want them to be able to log in via the Part-DB login
|
||||
form, you can use the `php bin/console partdb:user:convert-to-saml-user --to-local username` command to convert them to
|
||||
local users. You have to set a password for the user afterward.
|
||||
|
||||
{: .important }
|
||||
> It is recommended that you let the original admin user (ID: 2) be a local user, so you can still login to Part-DB if the SAML identity provider is not available.
|
||||
> It is recommended that you let the original admin user (ID: 2) be a local user, so you can still login to Part-DB if
|
||||
> the SAML identity provider is not available.
|
||||
|
||||
## Advanced SAML configuration
|
||||
You can find some more advanced SAML configuration options in the `config/packages/nbgrp_onelogin_saml.yaml` file. Refer to the file for more information.
|
||||
|
||||
You can find some more advanced SAML configuration options in the `config/packages/nbgrp_onelogin_saml.yaml` file. Refer
|
||||
to the file for more information.
|
||||
Normally you don't have to change anything here.
|
||||
|
||||
Please note that this file is not saved by the Part-DB backup tool, so you have to save it manually if you want to keep your changes. On docker containers you have to configure a volume mapping for it.
|
||||
Please note that this file is not saved by the Part-DB backup tool, so you have to save it manually if you want to keep
|
||||
your changes. On docker containers you have to configure a volume mapping for it.
|
||||
|
||||
## SAML behind a reverse proxy
|
||||
|
||||
If you are running Part-DB behind a reverse proxy, configure the `TRUSTED_PROXIES` environment and other reverse proxy
|
||||
settings as described in the [reverse proxy guide]({% link installation/reverse-proxy.md %}).
|
||||
If you want to use SAML you also need to set `SAML_BEHIND_PROXY` to `true` to enable the SAML proxy mode.
|
||||
|
||||
@@ -11,12 +11,13 @@ nav_order: 101
|
||||
|
||||
This guide describes how to migrate from [PartKeepr](https://partkeepr.org/) to Part-DB.
|
||||
|
||||
Part-DB has a built-in migration tool, which can be used to migrate the data from an existing PartKeepr instance to
|
||||
Part-DB has a built-in migration tool, which can be used to migrate the data from an existing PartKeepr instance to
|
||||
a new Part-DB instance. Most of the data can be migrated, however there are some limitations, you can find below.
|
||||
|
||||
|
||||
## What can be imported
|
||||
|
||||
* Datastructures (Categories, Footprints, Storage Locations, Manufacturers, Distributors, Part Measurement Units)
|
||||
* Basic part informations (Name, Description, Comment, etc.)
|
||||
* Basic part information's (Name, Description, Comment, etc.)
|
||||
* Attachments and images of parts, projects, footprints, manufacturers and storage locations
|
||||
* Part prices (distributor infos)
|
||||
* Part parameters
|
||||
@@ -24,9 +25,11 @@ a new Part-DB instance. Most of the data can be migrated, however there are some
|
||||
* Users (optional): Passwords however will be not migrated, and need to be reset later
|
||||
|
||||
## What can't be imported
|
||||
|
||||
* Metaparts (A dummy version of the metapart will be created in Part-DB, however it will not function as metapart)
|
||||
* Multiple manufacturers per part (only the last manufacturer of a part will be migrated)
|
||||
* Overage information for project parts (the overage info will be set as comment in the project BOM, but will have no effect)
|
||||
* Overage information for project parts (the overage info will be set as comment in the project BOM, but will have no
|
||||
effect)
|
||||
* Batch Jobs
|
||||
* Parameter Units (the units will be written into the parameters)
|
||||
* Project Reports and Project Runs
|
||||
@@ -34,18 +37,31 @@ a new Part-DB instance. Most of the data can be migrated, however there are some
|
||||
* Any kind of PartKeepr preferences
|
||||
|
||||
## How to migrate
|
||||
1. Install Part-DB like described in the installation guide. You can use any database backend you want (mysql or sqlite). Run the database migration, but do not create any new data yet.
|
||||
2. Export your PartKeepr database as XML file using [mysqldump](https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html):
|
||||
When the MySQL database is running on the local computer and you are root you can just run the command `mysqldump --xml PARTKEEPR_DATABASE --result-file pk.xml`.
|
||||
If your server is remote or your MySQL authentication is different, you need to run `mysqldump --xml -h PARTKEEPR_HOST -u PARTKEEPR_USER -p PARTKEEPR_DATABASE`, where you replace `PARTKEEPR_HOST`
|
||||
with the hostname of your MySQL database and `PARTKEEPR_USER` with the username of MySQL user which has access to the PartKeepr database. You will be asked for the MySQL user password.
|
||||
3. Go the Part-DB main folder and run the command `php bin/console partdb:migrations:import-partkeepr path/to/pk.xml`. This step will delete all existing data in the Part-DB database and import the contents of PartKeepr.
|
||||
4. Copy the contents of `data/files/` from your PartKeepr installation to the `uploads/` folder of your Part-DB installation and the contents of `data/images` from PartKeepr to `public/media/` of Part-DB.
|
||||
|
||||
1. Install Part-DB like described in the installation guide. You can use any database backend you want (mysql or
|
||||
sqlite). Run the database migration, but do not create any new data yet.
|
||||
2. Export your PartKeepr database as XML file using [mysqldump](https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html):
|
||||
When the MySQL database is running on the local computer, and you are root you can just run the
|
||||
command `mysqldump --xml PARTKEEPR_DATABASE --result-file pk.xml`.
|
||||
If your server is remote or your MySQL authentication is different, you need to
|
||||
run `mysqldump --xml -h PARTKEEPR_HOST -u PARTKEEPR_USER -p PARTKEEPR_DATABASE`, where you replace `PARTKEEPR_HOST`
|
||||
with the hostname of your MySQL database and `PARTKEEPR_USER` with the username of MySQL user which has access to the
|
||||
PartKeepr database. You will be asked for the MySQL user password.
|
||||
3. Go the Part-DB main folder and run the command `php bin/console partdb:migrations:import-partkeepr path/to/pk.xml`.
|
||||
This step will delete all existing data in the Part-DB database and import the contents of PartKeepr.
|
||||
4. Copy the contents of `data/files/` from your PartKeepr installation to the `uploads/` folder of your Part-DB
|
||||
installation and the contents of `data/images` from PartKeepr to `public/media/` of Part-DB.
|
||||
5. Clear the cache of Part-DB by running: `php bin/console cache:clear`
|
||||
6. Go to the Part-DB web interface. You can login with the username `admin` and the password, which is shown during the installation process of Part-DB (step 1). You should be able to see all the data from PartKeepr.
|
||||
6. Go to the Part-DB web interface. You can log in with the username `admin` and the password, which is shown during the
|
||||
installation process of Part-DB (step 1). You should be able to see all the data from PartKeepr.
|
||||
|
||||
## Import users
|
||||
If you want to import the users (mostly the username and email address) from PartKeepr, you can add the `--import-users` option on the database import command (step 3): `php bin/console partdb:migrations:import-partkeepr --import-users path/to/pk.xml`.
|
||||
|
||||
All imported users of PartKeepr will be assigned to a new group "PartKeepr Users", which has normal user permissions (so editing data, but no administrative tasks). You can change the group and permissions later in Part-DB users managment.
|
||||
Passwords can not be imported from PartKeepr and all imported users get marked as disabled user. So to allow users to login, you need to enable them in the user management and assign a password.
|
||||
If you want to import the users (mostly the username and email address) from PartKeepr, you can add the `--import-users`
|
||||
option on the database import command (step 3):
|
||||
`php bin/console partdb:migrations:import-partkeepr --import-users path/to/pk.xml`.
|
||||
|
||||
All imported users of PartKeepr will be assigned to a new group "PartKeepr Users", which has normal user permissions (so
|
||||
editing data, but no administrative tasks). You can change the group and permissions later in Part-DB users management.
|
||||
Passwords can not be imported from PartKeepr and all imported users get marked as disabled user. So to allow users to
|
||||
login, you need to enable them in the user management and assign a password.
|
||||
@@ -4,18 +4,26 @@ title: Troubleshooting
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
Sometimes things go wrong and Part-DB shows an error message. This page should help you to solve the problem.
|
||||
|
||||
## Error messages
|
||||
When a common, easy fixable error occurs (like a non up-to-date database), Part-DB will show you some short instructions on how to fix the problem. If you have a problem that is not listed here, please open an issue on GitHub.
|
||||
|
||||
When a common, easy fixable error occurs (like a non-up-to-date database), Part-DB will show you some short instructions
|
||||
on how to fix the problem. If you have a problem that is not listed here, please open an issue on GitHub.
|
||||
|
||||
## General procedure
|
||||
|
||||
If you encounter an error, try the following steps:
|
||||
* Clear cache of Part-DB with the console command:
|
||||
|
||||
* Clear cache of Part-DB with the console command:
|
||||
|
||||
```bash
|
||||
php bin/console cache:clear
|
||||
```
|
||||
* Check if the database needs an update (and perform it when needed) with the console command:
|
||||
|
||||
* Check if the database needs an update (and perform it when needed) with the console command:
|
||||
|
||||
```bash
|
||||
php bin/console doctrine:migrations:migrate
|
||||
```
|
||||
@@ -23,16 +31,19 @@ php bin/console doctrine:migrations:migrate
|
||||
If this does not help, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony).
|
||||
|
||||
## Search for user and reset password:
|
||||
You can list all users with the following command: `php bin/console partdb:users:list`
|
||||
To reset the password of a user you can use the following command: `php bin/console partdb:users:set-password [username]`
|
||||
|
||||
You can list all users with the following command: `php bin/console partdb:users:list`
|
||||
To reset the password of a user you can use the following
|
||||
command: `php bin/console partdb:users:set-password [username]`
|
||||
|
||||
## Error logs
|
||||
|
||||
Detailed error logs can be found in the `var/log` directory.
|
||||
When Part-DB is installed directly, the errors are written to the `var/log/prod.log` file.
|
||||
|
||||
When Part-DB is installed with Docker, the errors are written directly to the console output.
|
||||
When Part-DB is installed with Docker, the errors are written directly to the console output.
|
||||
You can see the logs with the following command, when you are in the folder with the `docker-compose.yml` file
|
||||
|
||||
```bash
|
||||
docker-compose logs -f
|
||||
```
|
||||
@@ -40,4 +51,5 @@ docker-compose logs -f
|
||||
Please include the error logs in your issue on GitHub, if you open an issue.
|
||||
|
||||
## Report Issue
|
||||
|
||||
If an error occurs, or you found a bug, please [open an issue on GitHub](https://github.com/Part-DB/Part-DB-symfony).
|
||||
|
||||
@@ -6,55 +6,86 @@ nav_order: 100
|
||||
|
||||
# Upgrade from legacy Part-DB version
|
||||
|
||||
Part-DB 1.0 was a complete rewrite of the old Part-DB (< 1.0.0), which you can find [here](https://github.com/Part-DB/Part-DB). A lot of things changed internally, but Part-DB was always developed with compatibility in mind, so you can migrate smoothly to the new Part-DB version, and utilize its new features and improvements.
|
||||
Part-DB 1.0 was a complete rewrite of the old Part-DB (< 1.0.0), which you can
|
||||
find [here](https://github.com/Part-DB/Part-DB). A lot of things changed internally, but Part-DB was always developed
|
||||
with compatibility in mind, so you can migrate smoothly to the new Part-DB version, and utilize its new features and
|
||||
improvements.
|
||||
|
||||
Some things changed however to the old version and some features are still missing, so be sure to read the following sections carefully before proceeding to upgrade.
|
||||
Some things changed however to the old version and some features are still missing, so be sure to read the following
|
||||
sections carefully before proceeding to upgrade.
|
||||
|
||||
## Changes
|
||||
* PHP 7.4 or higher is required now (Part-DB 0.5 required PHP 5.4+, Part-DB 0.6 PHP 7.0).
|
||||
PHP 7.4 (or newer) is shipped by all current major Linux distros now (and can be installed by third party sources on others),
|
||||
Releases are available for Windows too, so almost everybody should be able to use PHP 7.4
|
||||
* **Console access highly required.** The installation of composer and frontend dependencies require console access, also more sensitive stuff like database migration work via CLI now, so you should have console access on your server.
|
||||
|
||||
* PHP 8.1 or higher is required now (Part-DB 0.5 required PHP 5.4+, Part-DB 0.6 PHP 7.0).
|
||||
Releases are available for Windows too, so almost everybody should be able to use PHP 8.1
|
||||
* **Console access highly required.** The installation of composer and frontend dependencies require console access,
|
||||
also more sensitive stuff like database migration work via CLI now, so you should have console access on your server.
|
||||
* Markdown/HTML is now used instead of BBCode for rich text in description and command fields.
|
||||
It is possible to migrate your existing BBCode to Markdown via `php bin/console php bin/console partdb:migrations:convert-bbcode`.
|
||||
* Server exceptions are not logged to Event log anymore. For security reasons (exceptions can contain sensitive informations)
|
||||
exceptions are only logged to server log (by default under './var/log'), so only the server admins can access it.
|
||||
* Profile labels are now saved in Database (before they were saved in a seperate JSON file). **The profiles of legacy Part-DB versions can not be imported into new Part-DB 1.0**
|
||||
* Label placeholders now use the `[[PLACEHOLDER]]` format instead of `%PLACEHOLDER%`. Also some placeholders has changed.
|
||||
* Configuration is now done via configuration files / environment variables instead of the WebUI (this maybe change in the future).
|
||||
It is possible to migrate your existing BBCode to Markdown
|
||||
via `php bin/console php bin/console partdb:migrations:convert-bbcode`.
|
||||
* Server exceptions are not logged to event log anymore. For security reasons (exceptions can contain sensitive
|
||||
information)
|
||||
exceptions are only logged to server log (by default under './var/log'), so only the server admins can access it.
|
||||
* Profile labels are now saved in Database (before they were saved in a separate JSON file). **The profiles of legacy
|
||||
Part-DB versions can not be imported into new Part-DB 1.0**
|
||||
* Label placeholders now use the `[[PLACEHOLDER]]` format instead of `%PLACEHOLDER%`. Also, some placeholders has
|
||||
changed.
|
||||
* Configuration is now done via configuration files / environment variables instead of the WebUI (this maybe change in
|
||||
the future).
|
||||
* Database updated are now done via console instead of the WebUI
|
||||
* Permission system changed: **You will have to newly set the permissions of all users and groups!**
|
||||
* Import / Export file format changed. Fields must be english now (unlike in legacy Part-DB versions, where german fields in CSV were possible)
|
||||
and you maybe have to change the header line/field names of your CSV files.
|
||||
* Import / Export file format changed. Fields must be english now (unlike in legacy Part-DB versions, where german
|
||||
fields in CSV were possible)
|
||||
and you maybe have to change the header line/field names of your CSV files.
|
||||
|
||||
## Missing features
|
||||
|
||||
* No possibility to mark parts for ordering (yet)
|
||||
* No support for 3D models of footprints (yet)
|
||||
* No possibility to disable footprints, manufacturers globally (or per category). This should not have a big impact, when you forbid users to edit/create them.
|
||||
* No possibility to disable footprints, manufacturers globally (or per category). This should not have a big impact,
|
||||
when you forbid users to edit/create them.
|
||||
* No resistor calculator or SMD labels tools
|
||||
|
||||
## Upgrade process
|
||||
|
||||
{: .warning }
|
||||
> Once you have upgraded the database to the latest version, you will not be able to access the database with Part-DB 0.5.*. Doing so could lead to data corruption. So make a a backup before you proceed the upgrade, so you will be able to revert the upgrade, when you are not happy with the new version
|
||||
> Once you have upgraded the database to the latest version, you will not be able to access the database with Part-DB
|
||||
> 0.5.*. Doing so could lead to data corruption. So make a backup before you proceed the upgrade, so you will be able to
|
||||
> revert the upgrade, when you are not happy with the new version
|
||||
>
|
||||
> Beware that all user and group permissions will be reset, and you have to set the permissions again
|
||||
> Beware that all user and group permissions will be reset, and you have to set the permissions again
|
||||
> the new Part-DB as many permissions changed, and automatic migration is not possible.
|
||||
|
||||
1. Upgrade your existing Part-DB version the newest Part-DB 0.5.* version (in the moment Part-DB 0.5.8), like described in the old Part-DB's repository.
|
||||
2. Make a backup of your database and attachments. If somethings goes wrong during migration, you can use this backup to start over. If you have some more complex permission configuration, you maybe want to do screenshots of it, so you can redo it again later.
|
||||
3. Setup the new Part-DB like described in installation section. You will need to do the setup for a MySQL instance (either via docker or direct installation). Set the `DATABASE_URL` environment variable in your `.env.local` (or `docker-compose.yaml`) to your existing database. (e.g. `DATABASE_URL=mysql://PARTDB_USER:PASSWORD@localhost:3306/DATABASE_NAME`)
|
||||
4. Ensure that the correct base currency is configured (`BASE_CURRENCY` env), this must match the currency used in the old Part-DB version. If you used Euro, you do not need to change anything.
|
||||
5. Run `php bin/console cache:clear` and `php bin/console doctrine:migrations:migrate`.
|
||||
4. Run `php bin/console partdb:migrations:convert-bbcode` to convert the BBCode used in comments and part description to the newly used markdown.
|
||||
5. Copy the content of the `data/media` folder from the old Part-DB instance into `public/media` folder in the new version.
|
||||
6. Run `php bin/console cache:clear`
|
||||
7. You should be able to login to Part-DB now using your admin account and the old password. If you do not know the admin username, run `php bin/console partdb:users:list` and look for the user with ID 1. You can reset the password of this user using `php bin/console partdb:users:set-password [username]`.
|
||||
8. All other users besides the admin user are disabled (meaning they can not login). Go to "System->User" and "System->Group" and check the permissions of the users (and change them if needed). If you are done enable the users again, by removing the disabled checkmark in the password section. If you have a lot of users you can enable them all at once using `php bin/console partdb:users:enable --all`
|
||||
|
||||
**It is not possible to access the database using the old Part-DB version.
|
||||
If you do so, this could damage your database.** Therefore it is recommended to remove the old Part-DB version, after everything works.
|
||||
1. Upgrade your existing Part-DB version the newest Part-DB 0.5.* version (at the moment Part-DB 0.5.8), like described
|
||||
in the old Part-DB's repository.
|
||||
2. Make a backup of your database and attachments. If something goes wrong during migration, you can use this backup to
|
||||
start over. If you have some more complex permission configuration, you maybe want to do screenshots of it, so you
|
||||
can redo it again later.
|
||||
3. Set up the new Part-DB like described in installation section. You will need to do the setup for a MySQL instance (
|
||||
either via docker or direct installation). Set the `DATABASE_URL` environment variable in your `.env.local` (
|
||||
or `docker-compose.yaml`) to your existing database. (
|
||||
e.g. `DATABASE_URL=mysql://PARTDB_USER:PASSWORD@localhost:3306/DATABASE_NAME`)
|
||||
4. Ensure that the correct base currency is configured (`BASE_CURRENCY` env), this must match the currency used in the
|
||||
old Part-DB version. If you used Euro, you do not need to change anything.
|
||||
5. Run `php bin/console cache:clear` and `php bin/console doctrine:migrations:migrate`.
|
||||
6. Run `php bin/console partdb:migrations:convert-bbcode` to convert the BBCode used in comments and part description to
|
||||
the newly used markdown.
|
||||
7. Copy the content of the `data/media` folder from the old Part-DB instance into `public/media` folder in the new
|
||||
version.
|
||||
8. Run `php bin/console cache:clear`
|
||||
9. You should be able to log in to Part-DB now using your admin account and the old password. If you do not know the
|
||||
admin username, run `php bin/console partdb:users:list` and look for the user with ID 1. You can reset the password
|
||||
of this user using `php bin/console partdb:users:set-password [username]`.
|
||||
10. All other users besides the admin user are disabled (meaning they can not log in). Go to "System->User" and "System->
|
||||
Group" and check the permissions of the users (and change them if needed). If you are done enable the users again, by
|
||||
removing the disabled checkmark in the password section. If you have a lot of users you can enable them all at once
|
||||
using `php bin/console partdb:users:enable --all`
|
||||
|
||||
**It is not possible to access the database using the old Part-DB version.
|
||||
If you do so, this could damage your database.** Therefore, it is recommended to remove the old Part-DB version, after
|
||||
everything works.
|
||||
|
||||
## Issues
|
||||
If you encounter any issues (especially during the database migration) or features do not work like intended, please open an issue ticket at GitHub.
|
||||
|
||||
If you encounter any issues (especially during the database migration) or features do not work like intended, please
|
||||
open an issue ticket at GitHub.
|
||||
|
||||
@@ -6,48 +6,75 @@ parent: Usage
|
||||
|
||||
# Backup and Restore Data
|
||||
|
||||
When working productively you should backup the data and configuration of Part-DB regularly to prevent data loss. This is also useful, if you want to migrate your Part-DB instance from one server to another. In that case you just have to backup the data on server 1, move the backup to server 2, install Part-DB on server 2 and restore the backup.
|
||||
When working productively you should back up the data and configuration of Part-DB regularly to prevent data loss. This
|
||||
is also useful, if you want to migrate your Part-DB instance from one server to another. In that case you just have to
|
||||
back up the data on server 1, move the backup to server 2, install Part-DB on server 2 and restore the backup.
|
||||
|
||||
## Backup (automatic / Part-DB supported)
|
||||
Part-DB includes a command `php bin/console partdb:backup` which automatically collects all the needed data (described below) and saves them to a ZIP file.
|
||||
|
||||
Part-DB includes a command `php bin/console partdb:backup` which automatically collects all the needed data (described
|
||||
below) and saves them to a ZIP file.
|
||||
|
||||
If you are using a MySQL/MariaDB database you need to have `mysqldump` installed and added to your `$PATH` env.
|
||||
|
||||
### Usage
|
||||
To backup all possible data, run the following command: `php bin/console partdb:backup --full /path/to/backup/partdb_backup.zip`.
|
||||
|
||||
It is possible to do only partial backups (config, attachments, or database). See `php bin/console partdb:backup --help` for more infos about these options.
|
||||
To back up all possible data, run the following
|
||||
command: `php bin/console partdb:backup --full /path/to/backup/partdb_backup.zip`.
|
||||
|
||||
It is possible to do only partial backups (config, attachments, or database). See `php bin/console partdb:backup --help`
|
||||
for more infos about these options.
|
||||
|
||||
## Backup (manual)
|
||||
There are 3 parts which have to be backup-ed: The configuration files, which contains the instance specific options, the uploaded files of attachments, and the database containing the most data of Part-DB.
|
||||
|
||||
There are 3 parts which have to be backup-ed: The configuration files, which contains the instance specific options, the
|
||||
uploaded files of attachments, and the database containing the most data of Part-DB.
|
||||
Everything else like thumbnails and cache files, are recreated automatically when needed.
|
||||
|
||||
### Configuration files
|
||||
You have to copy the `.env.local` file and (if you have changed it) the `config/parameters.yaml` and `config/banner.md` to your backup location.
|
||||
|
||||
You have to copy the `.env.local` file and (if you have changed it) the `config/parameters.yaml` and `config/banner.md`
|
||||
to your backup location.
|
||||
|
||||
### Attachment files
|
||||
|
||||
You have to recursively copy the `uploads/` folder and the `public/media` folder to your backup location.
|
||||
|
||||
### Database
|
||||
|
||||
#### Sqlite
|
||||
If you are using sqlite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`) to your backup location.
|
||||
|
||||
If you are using sqlite, it is sufficient to just copy your `app.db` from your database location (normally `var/app.db`)
|
||||
to your backup location.
|
||||
|
||||
#### MySQL / MariaDB
|
||||
For MySQL / MariaDB you have to dump the database to an SQL file. You can do this manually with phpmyadmin, or you use [`mysqldump`](https://mariadb.com/kb/en/mariadb-dumpmysqldump/) to dump the database to an SQL file via command line interface (`mysqldump -uBACKUP -pPASSWORD DATABASE`)
|
||||
|
||||
For MySQL / MariaDB you have to dump the database to an SQL file. You can do this manually with phpmyadmin, or you
|
||||
use [`mysqldump`](https://mariadb.com/kb/en/mariadb-dumpmysqldump/) to dump the database to an SQL file via command line
|
||||
interface (`mysqldump -uBACKUP -pPASSWORD DATABASE`)
|
||||
|
||||
## Restore
|
||||
Install Part-DB as usual as described in the installation section, with the exception of the database creation / migration part. You have to use the same database type (sqlite or mysql) as on the back-uped server instance.
|
||||
|
||||
Install Part-DB as usual as described in the installation section, except the database creation / migration part. You
|
||||
have to use the same database type (sqlite or mysql) as on the back-up server instance.
|
||||
|
||||
### Restore configuration
|
||||
Copy configuration files `.env.local`, (and if existing) `config/parameters.yaml` and `config/banner.md` from the backup to your new Part-DB instance and overwrite the existing files there.
|
||||
|
||||
Copy configuration files `.env.local`, (and if existing) `config/parameters.yaml` and `config/banner.md` from the backup
|
||||
to your new Part-DB instance and overwrite the existing files there.
|
||||
|
||||
### Restore attachment files
|
||||
|
||||
Copy the `uploads/` and the `public/media/` folder from your backup into your new Part-DB folder.
|
||||
|
||||
### Restore database
|
||||
|
||||
#### Sqlite
|
||||
|
||||
Copy the backup-ed `app.db` into the database folder normally `var/app.db` in Part-DB root folder.
|
||||
|
||||
#### MySQL / MariaDB
|
||||
Recreate a database and user with the same credentials as before (or update the database credentials in the `.env.local` file).
|
||||
|
||||
Recreate a database and user with the same credentials as before (or update the database credentials in the `.env.local`
|
||||
file).
|
||||
Import the dumped SQL file from the backup into your new database.
|
||||
@@ -7,23 +7,30 @@ parent: Usage
|
||||
|
||||
# Import Bill of Material (BOM) for Projects
|
||||
|
||||
Part-DB supports the import of Bill of Material (BOM) files for projects. This allows you to directly import a BOM file from your ECAD software into your Part-DB project.
|
||||
Part-DB supports the import of Bill of Material (BOM) files for projects. This allows you to directly import a BOM file
|
||||
from your ECAD software into your Part-DB project.
|
||||
|
||||
|
||||
The import process is currently semi-automatic. This means Part-DB will take the BOM file and create entries for all parts in the BOM file in your project and assign fields like
|
||||
mountnames (e.g. 'C1, C2, C3'), quantity and more.
|
||||
However, you still have to assign the parts from Part-DB database to the entries (if applicable) after the import by hand,
|
||||
The import process is currently semi-automatic. This means Part-DB will take the BOM file and create entries for all
|
||||
parts in the BOM file in your project and assign fields like
|
||||
mount names (e.g. 'C1, C2, C3'), quantity and more.
|
||||
However, you still have to assign the parts from Part-DB database to the entries (if applicable) after the import by
|
||||
hand,
|
||||
as Part-DB can not know which part you had in mind when you designed your schematic.
|
||||
|
||||
## Usage
|
||||
|
||||
In the project view or edit click on the "Import BOM" button, below the BOM table. This will open a dialog where you can
|
||||
select the BOM file you want to import and some options for the import process:
|
||||
|
||||
* **Type**: The format/type of the BOM file. See below for explanations of the different types.
|
||||
* **Clear existing BOM entries before import**: If this is checked, all existing BOM entries, which are currently associated with the project, will be deleted before the import.
|
||||
* **Clear existing BOM entries before import**: If this is checked, all existing BOM entries, which are currently
|
||||
associated with the project, will be deleted before the import.
|
||||
|
||||
### Supported BOM file formats
|
||||
|
||||
* **KiCAD Pcbnew BOM (CSV file)**: A CSV file of the Bill of Material (BOM) generated by [KiCAD Pcbnew](https://www.kicad.org/).
|
||||
Please note that you have to export the BOM from the PCB editor, the BOM generated by the schematic editor (Eeschema) has a different format and does not work with this type.
|
||||
You can generate this BOM file by going to "File" -> "Fabrication Outputs" -> "Bill of Materials" in Pcbnew and save the file to your desired location.
|
||||
* **KiCAD Pcbnew BOM (CSV file)**: A CSV file of the Bill of Material (BOM) generated
|
||||
by [KiCAD Pcbnew](https://www.kicad.org/).
|
||||
Please note that you have to export the BOM from the PCB editor, the BOM generated by the schematic editor (Eeschema)
|
||||
has a different format and does not work with this type.
|
||||
You can generate this BOM file by going to "File" -> "Fabrication Outputs" -> "Bill of Materials" in Pcbnew and save
|
||||
the file to your desired location.
|
||||
|
||||
@@ -10,29 +10,46 @@ Part-DB provides some console commands to display various information or perform
|
||||
The commands are invoked from the main directory of Part-DB with the command `php bin/console [command]` in the context
|
||||
of the database user (so usually the webserver user), so you maybe have to use `sudo` or `su` to execute the commands.
|
||||
|
||||
You can get help for every command with the parameter `--help`. See `php bin/console` for a list of all available commands.
|
||||
You can get help for every command with the parameter `--help`. See `php bin/console` for a list of all available
|
||||
commands.
|
||||
|
||||
## User management commands
|
||||
|
||||
## User managment commands
|
||||
* `php bin/console partdb:users:list`: List all users of this Part-DB instance
|
||||
* `php bin/console partdb:users:set-password [username]`: Set/Changes the password of the user with the given username. This allows administrators to reset a password of a user, if he forgot it.
|
||||
* `php bin/console partdb:users:enable [username]`: Enable/Disable the user with the given username (use `--disable` to disable the user, which prevents login)
|
||||
* `php bin/console partdb:users:set-password [username]`: Set/Changes the password of the user with the given username.
|
||||
This allows administrators to reset a password of a user, if he forgot it.
|
||||
* `php bin/console partdb:users:enable [username]`: Enable/Disable the user with the given username (use `--disable` to
|
||||
disable the user, which prevents login)
|
||||
* `php bin/console partdb:users:permissions`: View/Change the permissions of the user with the given username
|
||||
* `php bin/console partdb:users:upgrade-permissions-schema`: Upgrade the permissions schema of users to the latest version (this is normally automatically done when the user visits a page)
|
||||
* `php bin/console partdb:users:upgrade-permissions-schema`: Upgrade the permissions schema of users to the latest
|
||||
version (this is normally automatically done when the user visits a page)
|
||||
* `php bin/console partdb:logs:show`: Show the most recent entries of the Part-DB event log / recent activity
|
||||
* `php bin/console partdb:user:convert-to-saml-user`: Convert a local user to a SAML/SSO user. This is needed, if you want to use SAML/SSO authentication for a user, which was created before you enabled SAML/SSO authentication.
|
||||
* `php bin/console partdb:user:convert-to-saml-user`: Convert a local user to a SAML/SSO user. This is needed, if you
|
||||
want to use SAML/SSO authentication for a user, which was created before you enabled SAML/SSO authentication.
|
||||
|
||||
## Currency commands
|
||||
* `php bin/console partdb:currencies:update-exchange-rates`: Update the exchange rates of all currencies from the internet)
|
||||
|
||||
* `php bin/console partdb:currencies:update-exchange-rates`: Update the exchange rates of all currencies from the
|
||||
internet
|
||||
|
||||
## Installation/Maintenance commands
|
||||
|
||||
* `php bin/console partdb:backup`: Backup the database and the attachments
|
||||
* `php bin/console partdb:version`: Display the current version of Part-DB and the used PHP version
|
||||
* `php bin/console partdb:check-requirements`: Check if the requirements for Part-DB are met (PHP version, PHP extensions, etc.) and make suggestions what could be improved
|
||||
* `partdb:migrations:convert-bbcode`: Migrate the old BBCode markup codes used in legacy Part-DB versions (< 1.0.0) to the new markdown syntax
|
||||
* `partdb:attachments:clean-unused`: Remove all attachments which are not used by any database entry (e.g. orphaned attachments)
|
||||
* `partdb:cache:clear`: Clears all caches, so the next page load will be slower, but the cache will be rebuild. This can maybe fix some issues, when the cache were corrupted. This command is also needed after changing things in the `parameters.yaml` file or upgrading Part-DB.
|
||||
* `partdb:migrations:import-partkeepr`: Imports an mysqldump XML dump of a PartKeepr database into Part-DB. This is only needed for users, which want to migrate from PartKeepr to Part-DB. *All existing data in the Part-DB database is deleted!*
|
||||
* `php bin/console partdb:check-requirements`: Check if the requirements for Part-DB are met (PHP version, PHP
|
||||
extensions, etc.) and make suggestions what could be improved
|
||||
* `partdb:migrations:convert-bbcode`: Migrate the old BBCode markup codes used in legacy Part-DB versions (< 1.0.0) to
|
||||
the new Markdown syntax
|
||||
* `partdb:attachments:clean-unused`: Remove all attachments which are not used by any database entry (e.g. orphaned
|
||||
attachments)
|
||||
* `partdb:cache:clear`: Clears all caches, so the next page load will be slower, but the cache will be rebuilt. This can
|
||||
maybe fix some issues, when the cache were corrupted. This command is also needed after changing things in
|
||||
the `parameters.yaml` file or upgrading Part-DB.
|
||||
* `partdb:migrations:import-partkeepr`: Imports a mysqldump XML dump of a PartKeepr database into Part-DB. This is only
|
||||
needed for users, which want to migrate from PartKeepr to Part-DB. *All existing data in the Part-DB database is
|
||||
deleted!*
|
||||
|
||||
## Database commands
|
||||
|
||||
* `php bin/console doctrine:migrations:migrate`: Migrate the database to the latest version
|
||||
* `php bin/console doctrine:migrations:up-to-date`: Check if the database is up-to-date
|
||||
@@ -7,111 +7,146 @@ nav_order: 4
|
||||
# Getting started
|
||||
|
||||
After Part-DB you should begin with customizing the settings, and setting up the basic structures.
|
||||
Before starting its useful to read a bit about the [concepts of Part-DB]({% link concepts.md %}).
|
||||
Before starting, it's useful to read a bit about the [concepts of Part-DB]({% link concepts.md %}).
|
||||
|
||||
1. TOC
|
||||
{:toc}
|
||||
|
||||
## Customize config files
|
||||
|
||||
Before you start creating data structures, you should configure Part-DB to your needs by changing possible configuration options.
|
||||
This is done either via changing the `.env.local` file in a direct installation or by changing the env variables in your `docker-compose.yaml` file.
|
||||
Before you start creating data structures, you should configure Part-DB to your needs by changing possible configuration
|
||||
options.
|
||||
This is done either via changing the `.env.local` file in a direct installation or by changing the env variables in
|
||||
your `docker-compose.yaml` file.
|
||||
A list of possible configuration options, can be found [here]({% link configuration.md %}).
|
||||
|
||||
## Change password, Set up Two-Factor-Authentication & Customize User settings
|
||||
|
||||
If you have not already done, you should change your user password. You can do this in the user settings (available in the navigation bar drop down with the user symbol).
|
||||
If you have not already done, you should change your user password. You can do this in the user settings (available in
|
||||
the navigation bar drop down with the user symbol).
|
||||
|
||||

|
||||
|
||||
There you can also find the option, to set up Two Factor Authentication methods like Google Authenticator. Using this is highly recommended (especially if you have admin permissions) to increase the security of your account. (Two Factor Authentication even can be enforced for all members of a user group)
|
||||
There you can also find the option, to set up Two-Factor Authentication methods like Google Authenticator. Using this is
|
||||
highly recommended (especially if you have admin permissions) to increase the security of your account. (Two-Factor
|
||||
Authentication even can be enforced for all members of a user group)
|
||||
|
||||
In the user settings panel you can change account infos like your username, your first and last name (which will be shown alongside your username to identify you better), department information and your email address. The email address is used to send password reset mails, if your system is configured to use this.
|
||||
In the user settings panel you can change account infos like your username, your first and last name (which will be
|
||||
shown alongside your username to identify you better), department information and your email address. The email address
|
||||
is used to send password reset mails, if your system is configured to use this.
|
||||
|
||||

|
||||
|
||||
In the configuration tab you can also override global settings, like your preferred UI language (which will automatically be applied after login), the timezone you are in (and in which times will be shown for you), your preferred currency (all money values will be shown converted to this to you, if possible) and the theme that should be used.
|
||||
In the configuration tab you can also override global settings, like your preferred UI language (which will
|
||||
automatically be applied after login), the timezone you are in (and in which times will be shown for you), your
|
||||
preferred currency (all money values will be shown converted to this to you, if possible) and the theme that should be
|
||||
used.
|
||||
|
||||
## (Optional) Customize homepage banner
|
||||
|
||||
The banner which is shown on the homepage, can be customized/changed by changing the `config/banner.md` file with a text editor. You can use markdown and (safe) HTML here, to style and customize the banner.
|
||||
You can even use Latex style equations by wrapping the expressions into `$` (like `$E=mc^2$`, which is rendered inline: $E=mc^2$) or `$$` (like `$$E=mc^2$$`) which will be rendered as a block, like so: $$E=mc^2$$
|
||||
The banner which is shown on the homepage, can be customized/changed by changing the `config/banner.md` file with a text
|
||||
editor. You can use markdown and (safe) HTML here, to style and customize the banner.
|
||||
You can even use Latex style equations by wrapping the expressions into `$` (like `$E=mc^2$`, which is rendered inline:
|
||||
$E=mc^2$) or `$$` (like `$$E=mc^2$$`) which will be rendered as a block, like so: $$E=mc^2$$
|
||||
|
||||
## Create groups, users and customize permissions
|
||||
|
||||
### Users
|
||||
|
||||
When logged in as administrator, you can open the users menu in the `Tools` section of the sidebar under `System -> Users`.
|
||||
When logged in as administrator, you can open the users menu in the `Tools` section of the sidebar
|
||||
under `System -> Users`.
|
||||
At this page you can create new users, change their passwords and settings and change their permissions.
|
||||
For each user which should use Part-DB you should setup a own account, so that tracking of what user did what works properly.
|
||||
For each user which should use Part-DB you should set up an own account, so that tracking of what user did what works
|
||||
properly.
|
||||

|
||||
|
||||
|
||||
You should check the permissions for every user and ensure that they are in the intended way, and no user has more permissions than he needs.
|
||||
For each capability you can choose between allow, forbid and inherit. In the last case, the permission is determined by the group a user has (if no group is chosen, it equals forbid)
|
||||
You should check the permissions for every user and ensure that they are in the intended way, and no user has more
|
||||
permissions than he needs.
|
||||
For each capability you can choose between allow, forbid and inherit. In the last case, the permission is determined by
|
||||
the group a user has (if no group is chosen, it equals forbid)
|
||||
|
||||

|
||||
|
||||
|
||||
### Anonymous user
|
||||
|
||||
The `anonymous` user is special, as its settings and permissions are used for everybody who is not logged in. By default the anonymous user has read capabilities for your parts. If your Part-DB instance is publicly available you maybe want to restrict the permissions.
|
||||
The `anonymous` user is special, as its settings and permissions are used for everybody who is not logged in. By default,
|
||||
the anonymous user has read capabilities for your parts. If your Part-DB instance is publicly available you maybe want
|
||||
to restrict the permissions.
|
||||
|
||||
### Groups
|
||||
|
||||
If you have many users which should share the same permissions, it is useful to define the permissions using user groups, which you can create and edit in the `System -> Groups` menu.
|
||||
If you have many users which should share the same permissions, it is useful to define the permissions using user
|
||||
groups, which you can create and edit in the `System -> Groups` menu.
|
||||
|
||||
By default 3 groups are defined:
|
||||
* `readonly` which users have only have read permissions (like viewing, searching parts, attachments, etc.)
|
||||
|
||||
* `readonly` which users only have read permissions (like viewing, searching parts, attachments, etc.)
|
||||
* `users` which users also have rights to edit/delete/create elements
|
||||
* `admin` which users can do administrative operations (like creating new users, show global system log, etc.)
|
||||
|
||||
Users only use the setting of a capability from a group, if the user has a group associated and the capability on the user is set to `inherit` (which is the default if creating a new user). You can override the permissions settings of a group per user by explicitly settings the permission at the user.
|
||||
|
||||
Groups are organized as trees, meaning a group can have parent and child permissions and child groups can inherit permissions from their parents.
|
||||
To inherit the permissions from a parent group set the capability to inherit, otherwise set it explicitly to override the parents permission.
|
||||
Users only use the setting of a capability from a group, if the user has a group associated and the capability on the
|
||||
user is set to `inherit` (which is the default if creating a new user). You can override the permissions settings of a
|
||||
group per user by explicitly settings the permission at the user.
|
||||
|
||||
Groups are organized as trees, meaning a group can have parent and child permissions and child groups can inherit
|
||||
permissions from their parents.
|
||||
To inherit the permissions from a parent group set the capability to inherit, otherwise set it explicitly to override
|
||||
the parents' permission.
|
||||
|
||||
## Create Attachment types
|
||||
|
||||
Every attachment (that is an file associated with a part, data structure, etc.) must have an attachment type. They can be used to group attachments logically, like differentiating between datasheets, pictures and other documents.
|
||||
Every attachment (that is a file associated with a part, data structure, etc.) must have an attachment type. They can
|
||||
be used to group attachments logically, like differentiating between datasheets, pictures and other documents.
|
||||
|
||||
You can create/edit attachment types in the tools sidebar under "Edit -> Attachment types":
|
||||
|
||||

|
||||
|
||||
Depending on your usecase different entries here make sense. For part mananagment the following (additional) entries maybe make sense:
|
||||
|
||||
Depending on your use case different entries here make sense. For part management the following (additional) entries
|
||||
maybe make sense:
|
||||
|
||||
* Datasheets (restricted to pdfs, Allowed filetypes: `application/pdf`)
|
||||
* Pictures (for generic pictures of components, storage locations, etc., Allowed filetypes: `image/*`
|
||||
|
||||
For every attachment type a list of allowed file types, which can be uploaded to an attachment with this attachment type, can be defined. You can either pass a list of allowed file extensions (e.g. `.pdf, .zip, .docx`) and/or a list of [Mime Types](https://en.wikipedia.org/wiki/Media_type) (e.g. `application/pdf, image/jpeg`) or a combination of both here. To allow all browser supported images, you can use `image/*` wildcard here.
|
||||
For every attachment type a list of allowed file types, which can be uploaded to an attachment with this attachment
|
||||
type, can be defined. You can either pass a list of allowed file extensions (e.g. `.pdf, .zip, .docx`) and/or a list
|
||||
of [Mime Types](https://en.wikipedia.org/wiki/Media_type) (e.g. `application/pdf, image/jpeg`) or a combination of both
|
||||
here. To allow all browser supported images, you can use `image/*` wildcard here.
|
||||
|
||||
## (Optional) Create Currencies
|
||||
|
||||
If you want to save priceinformations for parts in a currency different to your global currency (by default Euro), you have to define the additional currencies you want to use under `Edit -> Currencies`:
|
||||
If you want to save price information for parts in a currency different to your global currency (by default Euro), you
|
||||
have to define the additional currencies you want to use under `Edit -> Currencies`:
|
||||
|
||||

|
||||
|
||||
You create a new currency, name it however you want (it is recommended to use the official name of the currency) and select the currency ISO code from the list and save it. The currency symbol is determined automatically from chose ISO code.
|
||||
You can define a exchange rate in terms of your base currency (e.g. how much euros is one unit of your currency worth) to convert the currencies values in your preferred display currency automatically.
|
||||
|
||||
You create a new currency, name it however you want (it is recommended to use the official name of the currency) and
|
||||
select the currency ISO code from the list and save it. The currency symbol is determined automatically from chose ISO
|
||||
code.
|
||||
You can define an exchange rate in terms of your base currency (e.g. how many euros is one unit of your currency worth)
|
||||
to convert the currencies values in your preferred display currency automatically.
|
||||
|
||||
## (Optional) Create Measurement Units
|
||||
|
||||
By default Part-DB assumes that the parts in inventory can be counted by individual indivisible pieces, like LEDs in a box or books in a shelf.
|
||||
However if you want to manage things, that are divisible and and the instock is described by a physical quantity, like length for cables, or volumina of a liquid, you have to define additional measurement units.
|
||||
By default, Part-DB assumes that the parts in inventory can be counted by individual indivisible pieces, like LEDs in a
|
||||
box or books in a shelf.
|
||||
However, if you want to manage things, that are divisible and the stock is described by a physical quantity, like
|
||||
length for cables, or volumina of a liquid, you have to define additional measurement units.
|
||||
|
||||
This is possible under `Edit -> Measurement Units`:
|
||||

|
||||
|
||||
You can give the measurement unit a name and an optional unit symbol (like `m` for meters) which is shown when quantities in this unit are displayed. The option `Use SI prefix` is useful for almost all physical quantities, as big and small numbers are automatically formatted with SI-prefixes (like 1.5kg instead 1500 grams).
|
||||
You can give the measurement unit a name and an optional unit symbol (like `m` for meters) which is shown when
|
||||
quantities in this unit are displayed. The option `Use SI prefix` is useful for almost all physical quantities, as big
|
||||
and small numbers are automatically formatted with SI-prefixes (like 1.5kg instead 1500 grams).
|
||||
|
||||
The measurement unit can be selected for each part individually, by setting the option in the advanced tab of a part`s edit menu.
|
||||
The measurement unit can be selected for each part individually, by setting the option in the advanced tab of a part`s
|
||||
edit menu.
|
||||
|
||||
## Create Categories
|
||||
|
||||
A category is used to group parts logically by their function (e.g. all NPN transistors would be put in a "NPN-Transistors" category).
|
||||
A category is used to group parts logically by their function (e.g. all NPN transistors would be put in a "
|
||||
NPN-Transistors" category).
|
||||
Categories are hierarchical structures meaning that you can create logical trees to group categories together.
|
||||
See [Concepts]({% link concepts.md %}) for an example tree structure.
|
||||
|
||||
@@ -121,43 +156,51 @@ Every part has to be assigned to a category, so you should create at least one c
|
||||
|
||||
## (Optional) Create Footprints
|
||||
|
||||
Footprints are used to describe the physical shape of a part, like a resistor or a capacitor.
|
||||
Footprints are used to describe the physical shape of a part, like a resistor or a capacitor.
|
||||
They can be used to group parts by their physical shape and to find parts with in the same package.
|
||||
|
||||
You can create/edit footprints in the tools sidebar under "Edit -> Footprints".
|
||||
|
||||
It is useful to create footprints for the most common packages, like SMD resistors, capacitors, etc. to make it easier to find parts with the same footprint.
|
||||
It is useful to create footprints for the most common packages, like SMD resistors, capacitors, etc. to make it easier
|
||||
to find parts with the same footprint.
|
||||
You should create these as a tree structure, so that you can group footprints by their type.
|
||||
See [Concepts]({% link concepts.md %}) for an example tree structure.
|
||||
|
||||
You can define attachments here which are associated with the footprint. The attachment set as preview image, will be
|
||||
used whenever a visual representation of the footprint is needed (e.g. in the part list).
|
||||
|
||||
For many common footprints, you can use the built-in footprints, which can be found in the "Builtin footprint image gallery", which you can find in the tools menu.
|
||||
Type the name of the image you want to use in the URL field of the attachment and select the image from the dropdown menu.
|
||||
For many common footprints, you can use the built-in footprints, which can be found in the "Builtin footprint image
|
||||
gallery", which you can find in the "tools" menu.
|
||||
Type the name of the image you want to use in the URL field of the attachment and select the image from the dropdown
|
||||
menu.
|
||||
|
||||
## (Optional) Create Storage locations
|
||||
|
||||
A storelocation represents a place where parts can be stored.
|
||||
A storage location represents a place where parts can be stored.
|
||||
|
||||
You can create/edit storage locations in the tools sidebar under "Edit -> Storage locations".
|
||||
|
||||
## (Optional) Create Manufacturers and suppliers
|
||||
|
||||
You can create/edit [manufacturers]({% link concepts.md %}#manufacturers) and [suppliers]({% link concepts.md %}#suppliers) in the tools sidebar under "Edit -> Manufacturers" and "Edit -> Suppliers".
|
||||
You can create/edit [manufacturers]({% link concepts.md %}#manufacturers) and [suppliers]({% link concepts.md
|
||||
%}#suppliers) in the tools sidebar under "Edit -> Manufacturers" and "Edit -> Suppliers".
|
||||
|
||||
## Create parts
|
||||
|
||||
You are now ready to create your first part. You can do this by clicking either by clicking "Edit -> New Part" in the tools sidebar tree
|
||||
or by clicking the "Create new Part" above the (empty) part list, after clicking on one of your newly created categories.
|
||||
You are now ready to create your first part. You can do this by clicking either by clicking "Edit -> New Part" in the
|
||||
tools sidebar tree
|
||||
or by clicking the "Create new Part" above the (empty) part list, after clicking on one of your newly created
|
||||
categories.
|
||||
|
||||
You will be presented with a form where you can enter the basic information about your part:
|
||||
|
||||

|
||||
|
||||
You have to enter at least a name for the part and choose a category for it, the other fields are optional.
|
||||
However, it is recommended to fill out as much information as possible, as this will make it easier to find the part later.
|
||||
However, it is recommended to fill out as much information as possible, as this will make it easier to find the part
|
||||
later.
|
||||
|
||||
You can choose from your created datastructures to add manufacturer information, supplier information, etc. to the part.
|
||||
You can also create new datastructures on the fly, if you want to add additional information to the part, by typing the
|
||||
name of the new datastructure in the field and select the "New ..." option in the dropdown menu. See [tips]({% link usage/tips_tricks.md %}) for more information.
|
||||
name of the new datastructure in the field and select the "New ..." option in the dropdown menu. See [tips]({% link
|
||||
usage/tips_tricks.md %}) for more information.
|
||||
@@ -7,50 +7,81 @@ parent: Usage
|
||||
|
||||
# Import & Export data
|
||||
|
||||
Part-DB offers the possibility to import existing data (parts, datastructures, etc.) from existing datasources into Part-DB. Data can also be exported from Part-DB into various formats.
|
||||
Part-DB offers the possibility to import existing data (parts, datastructures, etc.) from existing data sources into
|
||||
Part-DB. Data can also be exported from Part-DB into various formats.
|
||||
|
||||
## Import
|
||||
|
||||
{: .note }
|
||||
> As data import is a very powerful feature and can easily fill up your database with lots of data, import is by default only available for
|
||||
> administrators. If you want to allow other users to import data, or can not import data, check the permissions of the user. You can enable import for each data structure
|
||||
> As data import is a very powerful feature and can easily fill up your database with lots of data, import is by default
|
||||
> only available for
|
||||
> administrators. If you want to allow other users to import data, or can not import data, check the permissions of the
|
||||
> user. You can enable import for each data structure
|
||||
> individually in the permissions settings.
|
||||
|
||||
If you want to import data from PartKeepr you might want to look into the [PartKeepr migration guide]({% link upgrade_legacy.md %}).
|
||||
If you want to import data from PartKeepr you might want to look into the [PartKeepr migration guide]({% link
|
||||
upgrade_legacy.md %}).
|
||||
|
||||
### Import parts
|
||||
|
||||
Part-DB supports the import of parts from CSV files and other formats. This can be used to import existing parts from other databases or datasources into Part-DB. The import can be done via the "Tools -> Import parts" page, which you can find in the "Tools" sidebar panel.
|
||||
Part-DB supports the import of parts from CSV files and other formats. This can be used to import existing parts from
|
||||
other databases or data sources into Part-DB. The import can be done via the "Tools -> Import parts" page, which you can
|
||||
find in the "Tools" sidebar panel.
|
||||
|
||||
{: .important }
|
||||
> When importing data, the data is immediatley written to database during the import process, when the data is formally valid.
|
||||
> You will not be able to check the data before it is written to the database, so you should review the data before using the import tool.
|
||||
> When importing data, the data is immediately written to database during the import process, when the data is formally
|
||||
> valid.
|
||||
> You will not be able to check the data before it is written to the database, so you should review the data before
|
||||
> using the import tool.
|
||||
|
||||
You can upload the file which should be imported here and choose various options on how the data should be treated:
|
||||
* **Format**: By default "auto" is selected here and Part-DB will try to detect the format of the file automatically based on its file extension. If you want to force a specific format or Part-DB can not auto-detect the format, you can select it here.
|
||||
* **CSV delimiter**: If you upload an CSV file, you can select the delimiter character which is used to separate the columns in the CSV file. Depending on the CSV file, this might be a comma (`,`), semicolon (`;`).
|
||||
* **Category override**: You can select (or create) a category here, to which all imported parts should be assigned, no matter what was specified in the import file. This can be useful if you want to assign all imports to a certain category or if no category is specified in the data. If you leave this field empty, the category will be determined by the import file (or the export will error, if no category is specified).
|
||||
* **Mark all imported parts as "Needs review"**: If this is selected, all imported parts will be marked as "Needs review" after the import. This can be useful if you want to review all imported parts before using them.
|
||||
* **Create unknown datastructures**: If this is selected Part-DB will create new datastructures (like categories, manufacturers, etc.) if no datastructure(s) with the same name and path already exists. If this is not selected, only existing datastructures will be used and if no matching datastrucure is found, the imported parts field will be empty.
|
||||
* **Path delimiter**: Part-DB allows you to create/select nested datastructures (like categories, manufacturers, etc.) by using a path (e.g. `Category 1->Category 1.1`, which will select/create the `Category 1.1` whose parent is `Category 1`). This path is separated by the path delimiter. If you want to use a different path delimiter than the default one (which is `>`), you can select it here.
|
||||
* **Abort on validation error**: If this is selected, the import will be aborted if a validation error occurs (e.g. if a required field is empty) for any of the imported parts and validation errors will be shown on top of the page. If this is not selected, the import will continue for the other parts and only the invalid parts will be skipped.
|
||||
|
||||
After you have selected the options, you can start the import by clicking the "Import" button. When the import is finished, you will see the results of the import in the lower half of the page. You find a table with the imported parts (including links to them) there.
|
||||
* **Format**: By default "auto" is selected here and Part-DB will try to detect the format of the file automatically
|
||||
based on its file extension. If you want to force a specific format or Part-DB can not auto-detect the format, you can
|
||||
select it here.
|
||||
* **CSV delimiter**: If you upload an CSV file, you can select the delimiter character which is used to separate the
|
||||
columns in the CSV file. Depending on the CSV file, this might be a comma (`,`), semicolon (`;`).
|
||||
* **Category override**: You can select (or create) a category here, to which all imported parts should be assigned, no
|
||||
matter what was specified in the import file. This can be useful if you want to assign all imports to a certain
|
||||
category or if no category is specified in the data. If you leave this field empty, the category will be determined by
|
||||
the import file (or the export will error, if no category is specified).
|
||||
* **Mark all imported parts as "Needs review"**: If this is selected, all imported parts will be marked as "Needs
|
||||
review" after the import. This can be useful if you want to review all imported parts before using them.
|
||||
* **Create unknown datastructures**: If this is selected Part-DB will create new datastructures (like categories,
|
||||
manufacturers, etc.) if no datastructure(s) with the same name and path already exists. If this is not selected, only
|
||||
existing datastructures will be used and if no matching datastrucure is found, the imported parts field will be empty.
|
||||
* **Path delimiter**: Part-DB allows you to create/select nested datastructures (like categories, manufacturers, etc.)
|
||||
by using a path (e.g. `Category 1->Category 1.1`, which will select/create the `Category 1.1` whose parent
|
||||
is `Category 1`). This path is separated by the path delimiter. If you want to use a different path delimiter than the
|
||||
default one (which is `>`), you can select it here.
|
||||
* **Abort on validation error**: If this is selected, the import will be aborted if a validation error occurs (e.g. if a
|
||||
required field is empty) for any of the imported parts and validation errors will be shown on top of the page. If this
|
||||
is not selected, the import will continue for the other parts and only the invalid parts will be skipped.
|
||||
|
||||
After you have selected the options, you can start the import by clicking the "Import" button. When the import is
|
||||
finished, you will see the results of the import in the lower half of the page. You find a table with the imported
|
||||
parts (including links to them) there.
|
||||
|
||||
#### Fields description
|
||||
|
||||
For the importing of parts, you can use the following fields which will be imported into each part. Please note that the field names are case sensitive (so `name` is not the same as `Name`). All fields (besides name) are optional, so you can leave them empty or do not include the column in your file.
|
||||
For the importing of parts, you can use the following fields which will be imported into each part. Please note that the
|
||||
field names are case-sensitive (so `name` is not the same as `Name`). All fields (besides name) are optional, so you can
|
||||
leave them empty or do not include the column in your file.
|
||||
|
||||
* **`name`** (required): The name of the part. This is the only required field, all other fields are optional.
|
||||
* **`description`**: The description of the part, you can use markdown/HTML syntax here for rich text formatting.
|
||||
* **`notes`** or **`comment`**: The notes of the part, you can use markdown/HTML syntax here for rich text formatting.
|
||||
* **`category`**: The category of the part. This can be a path (e.g. `Category 1->Category 1.1`), which will select/create the `Category 1.1` whose parent is `Category 1`. If you want to use a different path delimiter than the default one (which is `->`), you can select it in the import options. If the category does not exist and the option "Create unknown datastructures" is selected, it will be created.
|
||||
* **`category`**: The category of the part. This can be a path (e.g. `Category 1->Category 1.1`), which will
|
||||
select/create the `Category 1.1` whose parent is `Category 1`. If you want to use a different path delimiter than the
|
||||
default one (which is `->`), you can select it in the import options. If the category does not exist and the option "
|
||||
Create unknown datastructures" is selected, it will be created.
|
||||
* **`footprint`**: The footprint of the part. Can be a path similar to the category field.
|
||||
* **`favorite`**: If this is set to `1`, the part will be marked as favorite.
|
||||
* **`manufacturer`**: The manufacturer of the part. Can be a path similar to the category field.
|
||||
* **`manufacturer_product_number`** or **`mpn`**: The manufacturer product number of the part.
|
||||
* **`manufacturer_product_url`: The URL to the product page of the manufacturer of the part.
|
||||
* **`manufacturing_status`**: The manufacturing status of the part, must be one of the following values: `announced`, `active`, `nrfnd`, `eol`, `discontinued` or left empty.
|
||||
* **`manufacturer_product_url`**: The URL to the product page of the manufacturer of the part.
|
||||
* **`manufacturing_status`**: The manufacturing status of the part, must be one of the following
|
||||
values: `announced`, `active`, `nrfnd`, `eol`, `discontinued` or left empty.
|
||||
* **`needs_review`** or **`needs_review`**: If this is set to `1`, the part will be marked as "needs review".
|
||||
* **`tags`**: A comma separated list of tags for the part.
|
||||
* **`mass`**: The mass of the part in grams.
|
||||
@@ -58,46 +89,73 @@ For the importing of parts, you can use the following fields which will be impor
|
||||
* **`minamount`**: The minimum amount of the part which should be in stock.
|
||||
* **`partUnit`**: The measurement unit of the part to use. Can be a path similar to the category field.
|
||||
|
||||
With the following fields you can specify storage locations and amount / quantiy in stock of the part. An PartLot will be created automatically from the data and assigned to the part. The following fields are helpers for an easy import for parts at one storage location. If you need to create a Part with multiple PartLots you have to use JSON format (or CSV) with nested objects:
|
||||
With the following fields you can specify storage locations and amount / quantity in stock of the part. An PartLot will
|
||||
be created automatically from the data and assigned to the part. The following fields are helpers for an easy import for
|
||||
parts at one storage location. If you need to create a Part with multiple PartLots you have to use JSON format (or CSV)
|
||||
with nested objects:
|
||||
|
||||
**`storage_location`** or **`storelocation`**: The storage location of the part. Can be a path similar to the category field.
|
||||
**`amount`**, **`quantity`** or **`instock`**: The amount of the part in stock. If this value is not set, the part lot will be marked with "unknown amount"
|
||||
**`storage_location`** or **`storelocation`**: The storage location of the part. Can be a path similar to the category
|
||||
field.
|
||||
**`amount`**, **`quantity`** or **`instock`**: The amount of the part in stock. If this value is not set, the part lot
|
||||
will be marked with "unknown amount"
|
||||
|
||||
The following fields can be used to specify the supplier/distributor, supplier product number and the price of the part. This is only possible for a single supplier/distributor and price with this fields. If you need to specify multiple suppliers/distributors or prices, you have to use JSON format (or CSV) with nested objects.
|
||||
**Please note that the supplier fields is required, if you want to import prices or supplier product numbers.**. If the supplier is not specified, the price and supplier product number fields will be ignored:
|
||||
The following fields can be used to specify the supplier/distributor, supplier product number and the price of the part.
|
||||
This is only possible for a single supplier/distributor and price with these fields. If you need to specify multiple
|
||||
suppliers/distributors or prices, you have to use JSON format (or CSV) with nested objects.
|
||||
**Please note that the supplier fields is required, if you want to import prices or supplier product numbers**. If the
|
||||
supplier is not specified, the price and supplier product number fields will be ignored:
|
||||
|
||||
* **`supplier`**: The supplier of the part. Can be a path similar to the category field.
|
||||
* **`supplier_product_number`** or **`supplier_part_number`** or * **`spn`**: The supplier product number of the part.
|
||||
* **`price`**: The price of the part in the base currency of the database (by default euro).
|
||||
|
||||
#### Example data
|
||||
Here you can find some example data for the import of parts, you can use it as a template for your own import (especially the CSV file).
|
||||
|
||||
Here you can find some example data for the import of parts, you can use it as a template for your own import (
|
||||
especially the CSV file).
|
||||
|
||||
* [Part import CSV example]({% link assets/usage/import_export/part_import_example.csv %}) with all possible fields
|
||||
|
||||
## Export
|
||||
|
||||
By default every user, who can read the datastructure, can also export the data of this datastructure, as this does not give the user any additional information.
|
||||
By default, every user, who can read the datastructure, can also export the data of this datastructure, as this does not
|
||||
give the user any additional information.
|
||||
|
||||
### Exporting data structures (categories, manufacturers, etc.)
|
||||
You can export data structures (like categories, manufacturers, etc.) in the respective edit page (e.g. Tools Panel -> Edit -> Category).
|
||||
If you select a certain datastructure from your list, you can export it (and optionally all sub-datastructures) in the "Export" tab.
|
||||
If you want to export all datastructures of a certain type (e.g. all categories in your database), you can select the "Export all" function in the "Import / Export" tab of the "new element" page.
|
||||
|
||||
You can export data structures (like categories, manufacturers, etc.) in the respective edit page (e.g. Tools Panel ->
|
||||
Edit -> Category).
|
||||
If you select a certain datastructure from your list, you can export it (and optionally all sub-datastructures) in the "
|
||||
Export" tab.
|
||||
If you want to export all datastructures of a certain type (e.g. all categories in your database), you can select the "
|
||||
Export all" function in the "Import / Export" tab of the "new element" page.
|
||||
|
||||
You can select between the following export formats:
|
||||
* **CSV** (Comma Separated Values): A semicolon separated list of values, where every line represents an element. This format can be imported into Excel or LibreOffice Calc and is easy to work with. However it does not support nested datastructures or sub data (like parameters, attachments, etc.), very well (many columns are generated, as every possible sub data is exported as a separate column).
|
||||
* **JSON** (JavaScript Object Notation): A text-based format, which is easy to work with programming laguages. It supports nested datastructures and sub data (like parameters, attachments, etc.) very well. However it is not easy to work with in Excel or LibreOffice Calc and you maybe need to write some code to work with the exported data efficiently.
|
||||
* **YAML** (Yet another Markup Language): Very similar to JSON
|
||||
* **XML** (Extensible Markup Language): Good support with nested datastructures. Similar usecase as JSON and YAML.
|
||||
|
||||
Also you can select between the following export levels:
|
||||
* **CSV** (Comma Separated Values): A semicolon separated list of values, where every line represents an element. This
|
||||
format can be imported into Excel or LibreOffice Calc and is easy to work with. However, it does not support nested
|
||||
datastructures or sub data (like parameters, attachments, etc.), very well (many columns are generated, as every
|
||||
possible sub data is exported as a separate column).
|
||||
* **JSON** (JavaScript Object Notation): A text-based format, which is easy to work with programming languages. It
|
||||
supports nested datastructures and sub data (like parameters, attachments, etc.) very well. However, it is not easy to
|
||||
work with in Excel or LibreOffice Calc and you maybe need to write some code to work with the exported data
|
||||
efficiently.
|
||||
* **YAML** (Yet another Markup Language): Very similar to JSON
|
||||
* **XML** (Extensible Markup Language): Good support with nested datastructures. Similar use case as JSON and YAML.
|
||||
|
||||
Also, you can select between the following export levels:
|
||||
|
||||
* **Simple**: This will only export very basic information about the name (like the name, or description for parts)
|
||||
* **Extended**: This will export all commonly used information about this datastructure (like notes, options, etc)
|
||||
* **Extended**: This will export all commonly used information about this datastructure (like notes, options, etc.)
|
||||
* **Full**: This will export all available information about this datastructure (like all parameters, attachments)
|
||||
|
||||
Please note that the level will also be applied to all sub data or children elements. So if you select "Full" for a part, all the associated categories, manufacturers, footprints, etc. will also be exported with all available information, this can lead to very large export files.
|
||||
Please note that the level will also be applied to all sub data or children elements. So if you select "Full" for a
|
||||
part, all the associated categories, manufacturers, footprints, etc. will also be exported with all available
|
||||
information, this can lead to very large export files.
|
||||
|
||||
### Exporting parts
|
||||
You can export parts in all part tables. Select the parts you want via the checkbox in the table line and select the export format and level in the appearing menu.
|
||||
|
||||
You can export parts in all part tables. Select the parts you want via the checkbox in the table line and select the
|
||||
export format and level in the appearing menu.
|
||||
|
||||
See the section about exporting datastructures for more information about the export formats and levels.
|
||||
@@ -6,137 +6,208 @@ parent: Usage
|
||||
|
||||
# Information provider system
|
||||
|
||||
Part-DB can create parts based on information from external sources: For example with the right setup you can just search for a part number
|
||||
and Part-DB will query selected distributors and manufacturers for the part and create a part with the information it found.
|
||||
Part-DB can create parts based on information from external sources: For example with the right setup you can just
|
||||
search for a part number
|
||||
and Part-DB will query selected distributors and manufacturers for the part and create a part with the information it
|
||||
found.
|
||||
This way your Part-DB parts automatically get datasheet links, prices, parameters and more, with just a few clicks.
|
||||
|
||||
## Usage
|
||||
|
||||
Before you can use the information provider system, you have to configure at least one information provider, which act as data source.
|
||||
Before you can use the information provider system, you have to configure at least one information provider, which act
|
||||
as data source.
|
||||
See below for a list of available information providers and available configuration options.
|
||||
For many providers it is enough, to setup the API keys in the env configuration, some require an additional OAuth connection.
|
||||
You can list all enabled information providers in the browser at `https://your-partdb-instance.tld/tools/info_providers/providers` (you need the right permission for it, see below).
|
||||
For many providers it is enough, to set up the API keys in the env configuration, some require an additional OAuth
|
||||
connection.
|
||||
You can list all enabled information providers in the browser
|
||||
at `https://your-partdb-instance.tld/tools/info_providers/providers` (you need the right permission for it, see below).
|
||||
|
||||
To use the information provider system, your user need to have the right permissions. Go to the permission management page of
|
||||
To use the information provider system, your user need to have the right permissions. Go to the permission management
|
||||
page of
|
||||
a user or a group and assign the permissions of the "Info providers" group in the "Miscellaneous" tab.
|
||||
|
||||
If you have the required permission you will find in the sidebar in the "Tools" section the entry "Create part from info provider".
|
||||
Click this and you will land on a search page. Enter the part number you want to search for and select the information providers you want to use.
|
||||
If you have the required permission you will find in the sidebar in the "Tools" section the entry "Create part from info
|
||||
provider".
|
||||
Click this and you will land on a search page. Enter the part number you want to search for and select the information
|
||||
providers you want to use.
|
||||
|
||||
After you click Search, you will be presented with the results and can select the result that fits best.
|
||||
With a click on the blue plus button, you will be redirected to the part creation page with the information already filled in.
|
||||
After you click Search, you will be presented with the results and can select the result that fits best.
|
||||
With a click on the blue plus button, you will be redirected to the part creation page with the information already
|
||||
filled in.
|
||||
|
||||

|
||||
|
||||
## Alternative names
|
||||
|
||||
Part-DB tries to automatically find existing elements from your database for the information it got from the providers for fields like manufacturer, footprint, etc.
|
||||
For this it searches for a element with the same name (case-insensitive) as the information it got from the provider. So e.g. if the provider returns "EXAMPLE CORP" as manufacturer,
|
||||
Part-DB tries to automatically find existing elements from your database for the information it got from the providers
|
||||
for fields like manufacturer, footprint, etc.
|
||||
For this it searches for an element with the same name (case-insensitive) as the information it got from the provider. So
|
||||
e.g. if the provider returns "EXAMPLE CORP" as manufacturer,
|
||||
Part-DB will automatically select the element with the name "Example Corp" from your database.
|
||||
|
||||
As the names of these fields differ from provider to provider (and maybe not even normalized for the same provider), you
|
||||
As the names of these fields differ from provider to provider (and maybe not even normalized for the same provider), you
|
||||
can define multiple alternative names for an element (on their editing page).
|
||||
For example if define a manufacturer "Example Corp" with the alternative names "Example Corp.", "Example Corp", "Example Corp. Inc." and "Example Corporation",
|
||||
For example if define a manufacturer "Example Corp" with the alternative names "Example Corp.", "Example Corp", "Example
|
||||
Corp. Inc." and "Example Corporation",
|
||||
then the provider can return any of these names and Part-DB will still automatically select the right element.
|
||||
|
||||
If Part-DB finds no matching element, it will automatically create a new one, when you do not change the value before saving.
|
||||
If Part-DB finds no matching element, it will automatically create a new one, when you do not change the value before
|
||||
saving.
|
||||
|
||||
## Attachment types
|
||||
|
||||
The information provider system uses attachment types to differentiate between datasheets and image attachments.
|
||||
For this it will create a "Datasheet" and "Image" attachment type on the first run. You can change the names of these
|
||||
For this it will create a "Datasheet" and "Image" attachment type on the first run. You can change the names of these
|
||||
types in the attachment type settings (as long as you keep the "Datasheet"/"Image" in the alternative names field).
|
||||
|
||||
If you already have attachment types for images and datasheets and want the information provider system to use them, you can
|
||||
If you already have attachment types for images and datasheets and want the information provider system to use them, you
|
||||
can
|
||||
add the alternative names "Datasheet" and "Image" to the alternative names field of the attachment types.
|
||||
|
||||
## Data providers
|
||||
|
||||
The system tries to be as flexible as possible, so many different information sources can be used.
|
||||
Each information source is called am "info provider" and handles the communication with the external source.
|
||||
The providers are just a driver which handles the communication with the different external sources and converts them into a common format Part-DB understands.
|
||||
The providers are just a driver which handles the communication with the different external sources and converts them
|
||||
into a common format Part-DB understands.
|
||||
That way it is pretty easy to create new providers as they just need to do very little work.
|
||||
|
||||
Normally the providers utilize an API of a service, and you need to create a account at the provider and get an API key.
|
||||
Also there are limits on how many requests you can do per day or months, depending on the provider and your contract with them.
|
||||
Normally the providers utilize an API of a service, and you need to create an account at the provider and get an API key.
|
||||
Also, there are limits on how many requests you can do per day or months, depending on the provider and your contract
|
||||
with them.
|
||||
|
||||
The following providers are currently available and shipped with Part-DB:
|
||||
|
||||
(All trademarks are property of their respective owners. Part-DB is not affiliated with any of the companies.)
|
||||
|
||||
### Ocotpart
|
||||
The Octopart provider uses the [Octopart / Nexar API](https://nexar.com/api) to search for parts and getting informations.
|
||||
To use it you have to create an account at Nexar and create a new application on the [Nexar Portal](https://portal.nexar.com/).
|
||||
The name does not matter, but it is important that the application has access to the "Supply" scope.
|
||||
In the Authorization tab, you will find the client ID and client secret, which you have to enter in the Part-DB env configuration (see below).
|
||||
### Octopart
|
||||
|
||||
Please note that the Nexar API in the free plan is limited to 1000 results per month.
|
||||
That means if you search for a keyword and results in 10 parts, then 10 will be substracted from your monthly limit. You can see your current usage on the Nexar portal.
|
||||
Part-DB caches the search results internally, so if you have searched for a part before, it will not count against your monthly limit again, when you create it from the search results.
|
||||
The Octopart provider uses the [Octopart / Nexar API](https://nexar.com/api) to search for parts and getting
|
||||
information.
|
||||
To use it you have to create an account at Nexar and create a new application on
|
||||
the [Nexar Portal](https://portal.nexar.com/).
|
||||
The name does not matter, but it is important that the application has access to the "Supply" scope.
|
||||
In the Authorization tab, you will find the client ID and client secret, which you have to put in the Part-DB env
|
||||
configuration (see below).
|
||||
|
||||
Please note that the Nexar API in the free plan is limited to 1000 results per month.
|
||||
That means if you search for a keyword and results in 10 parts, then 10 will be subtracted from your monthly limit. You
|
||||
can see your current usage on the Nexar portal.
|
||||
Part-DB caches the search results internally, so if you have searched for a part before, it will not count against your
|
||||
monthly limit again, when you create it from the search results.
|
||||
|
||||
Following env configuration options are available:
|
||||
|
||||
* `PROVIDER_OCTOPART_CLIENT_ID`: The client ID you got from Nexar (mandatory)
|
||||
* `PROVIDER_OCTOPART_SECRET`: The client secret you got from Nexar (mandatory)
|
||||
* `PROVIDER_OCTOPART_CURRENCY`: The currency you want to get prices in if available (optional, 3 letter ISO-code, default: `EUR`). If an offer is only available in a certain currency,
|
||||
Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert it to your preferred currency.
|
||||
* `PROVIDER_OCOTPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code, default: `DE`). To get correct prices, you have to set this and the currency setting to the correct value.
|
||||
* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This affects how quickly your monthly limit is used up.
|
||||
* `PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS`: If set to `true`, only offers from [authorized sellers](https://octopart.com/authorized) will be returned (optional, default: `false`).
|
||||
* `PROVIDER_OCTOPART_CURRENCY`: The currency you want to get prices in if available (optional, 3 letter ISO-code,
|
||||
default: `EUR`). If an offer is only available in a certain currency,
|
||||
Part-DB will save the prices in their native currency, and you can use Part-DB currency conversion feature to convert
|
||||
it to your preferred currency.
|
||||
* `PROVIDER_OCOTPART_COUNTRY`: The country you want to get prices in if available (optional, 2 letter ISO-code,
|
||||
default: `DE`). To get correct prices, you have to set this and the currency setting to the correct value.
|
||||
* `PROVIDER_OCTOPART_SEARCH_LIMIT`: The maximum number of results to return per search (optional, default: `10`). This
|
||||
affects how quickly your monthly limit is used up.
|
||||
* `PROVIDER_OCTOPART_ONLY_AUTHORIZED_SELLERS`: If set to `true`, only offers
|
||||
from [authorized sellers](https://octopart.com/authorized) will be returned (optional, default: `false`).
|
||||
|
||||
**Attention**: If you change the octopart clientID after you have already used the provider, you have to remove the OAuth token in the Part-DB database. Remove the entry in the table `oauth_tokens` with the name `ip_octopart_oauth`.
|
||||
**Attention**: If you change the octopart clientID after you have already used the provider, you have to remove the
|
||||
OAuth token in the Part-DB database. Remove the entry in the table `oauth_tokens` with the name `ip_octopart_oauth`.
|
||||
|
||||
### Digi-Key
|
||||
The Digi-Key provider uses the [Digi-Key API](https://developer.digikey.com/) to search for parts and getting shopping information from [Digi-Key](https://www.digikey.com/).
|
||||
To use it you have to create an account at Digi-Key and get an API key on the [Digi-Key API page](https://developer.digikey.com/).
|
||||
You must create an organization there and create a "Production app". Most settings are not important, you just have to grant access to the "Product Information" API.
|
||||
You will get an Client ID and a Client Secret, which you have to enter in the Part-DB env configuration (see below).
|
||||
|
||||
The Digi-Key provider uses the [Digi-Key API](https://developer.digikey.com/) to search for parts and getting shopping
|
||||
information from [Digi-Key](https://www.digikey.com/).
|
||||
To use it you have to create an account at Digi-Key and get an API key on
|
||||
the [Digi-Key API page](https://developer.digikey.com/).
|
||||
You must create an organization there and create a "Production app". Most settings are not important, you just have to
|
||||
grant access to the "Product Information" API.
|
||||
You will get a Client ID and a Client Secret, which you have to put in the Part-DB env configuration (see below).
|
||||
|
||||
Following env configuration options are available:
|
||||
|
||||
* `PROVIDER_DIGIKEY_CLIENT_ID`: The client ID you got from Digi-Key (mandatory)
|
||||
* `PROVIDER_DIGIKEY_SECRET`: The client secret you got from Digi-Key (mandatory)
|
||||
* `PROVIDER_DIGIKEY_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`)
|
||||
* `PROVIDER_DIGIKEY_LANGUAGE`: The language you want to get the descriptions in (optional, default: `en`)
|
||||
* `PROVIDER_DIGIKEY_COUNTRY`: The country you want to get the prices for (optional, default: `DE`)
|
||||
|
||||
The Digi-Key provider needs an additional OAuth connection. To do this, go to the information provider list (`https://your-partdb-instance.tld/tools/info_providers/providers`),
|
||||
go the Digi-Key provider (in the disabled page) and click on the "Connect OAuth" button. You will be redirected to Digi-Key, where you have to login and grant access to the app.
|
||||
The Digi-Key provider needs an additional OAuth connection. To do this, go to the information provider
|
||||
list (`https://your-partdb-instance.tld/tools/info_providers/providers`),
|
||||
go the Digi-Key provider (in the disabled page) and click on the "Connect OAuth" button. You will be redirected to
|
||||
Digi-Key, where you have to log in and grant access to the app.
|
||||
To do this your user needs the "Manage OAuth tokens" permission from the "System" section in the "System" tab.
|
||||
The OAuth connection should only be needed once, but if you have any problems with the provider, just click the button again, to establish a new connection.
|
||||
The OAuth connection should only be needed once, but if you have any problems with the provider, just click the button
|
||||
again, to establish a new connection.
|
||||
|
||||
### TME
|
||||
The TME provider use the API of [TME](https://www.tme.eu/) to search for parts and getting shopping information from them.
|
||||
|
||||
The TME provider use the API of [TME](https://www.tme.eu/) to search for parts and getting shopping information from
|
||||
them.
|
||||
To use it you have to create an account at TME and get an API key on the [TME API page](https://developers.tme.eu/en/).
|
||||
You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see below).
|
||||
You have to generate a new anonymous key there and enter the key and secret in the Part-DB env configuration (see
|
||||
below).
|
||||
|
||||
Following env configuration options are available:
|
||||
* `PROVIDER_TME_KEY`: The API key you got from TME (mandatory)
|
||||
|
||||
* `PROVIDER_TME_KEY`: The API key you got from TME (mandatory)
|
||||
* `PROVIDER_TME_SECRET`: The API secret you got from TME (mandatory)
|
||||
* `PROVIDER_TME_CURRENCY`: The currency you want to get prices in (optional, default: `EUR`)
|
||||
* `PROVIDER_TME_LANGUAGE`: The language you want to get the descriptions in (`en`, `de` and `pl`) (optional, default: `en`)
|
||||
* `PROVIDER_TME_LANGUAGE`: The language you want to get the descriptions in (`en`, `de` and `pl`) (optional,
|
||||
default: `en`)
|
||||
* `PROVIDER_TME_COUNTRY`: The country you want to get the prices for (optional, default: `DE`)
|
||||
* `PROVIDER_TME_GET_GROSS_PRICES`: If this is set to `1` the prices will be gross prices (including tax), otherwise net prices (optional, default: `0`)
|
||||
* `PROVIDER_TME_GET_GROSS_PRICES`: If this is set to `1` the prices will be gross prices (including tax), otherwise net
|
||||
prices (optional, default: `0`)
|
||||
|
||||
### Farnell / Element14 / Newark
|
||||
The Farnell provider uses the [Farnell API](https://partner.element14.com/) to search for parts and getting shopping information from [Farnell](https://www.farnell.com/).
|
||||
You have to create an account at Farnell and get an API key on the [Farnell API page](https://partner.element14.com/).
|
||||
Register a new application there (settings does not matter, as long as you select the "Product Search API") and you will get an API key.
|
||||
|
||||
The Farnell provider uses the [Farnell API](https://partner.element14.com/) to search for parts and getting shopping
|
||||
information from [Farnell](https://www.farnell.com/).
|
||||
You have to create an account at Farnell and get an API key on the [Farnell API page](https://partner.element14.com/).
|
||||
Register a new application there (settings does not matter, as long as you select the "Product Search API") and you will
|
||||
get an API key.
|
||||
|
||||
Following env configuration options are available:
|
||||
* `PROVIDER_ELEMENT14_KEY`: The API key you got from Farnell (mandatory)
|
||||
* `PROVIDER_ELEMENT14_STORE_ID`: The store ID you want to use. This decides the language of results, currency and country of prices (optional, default: `de.farnell.com`, see [here](https://partner.element14.com/docs/Product_Search_API_REST__Description) for availailable values)
|
||||
|
||||
* `PROVIDER_ELEMENT14_KEY`: The API key you got from Farnell (mandatory)
|
||||
* `PROVIDER_ELEMENT14_STORE_ID`: The store ID you want to use. This decides the language of results, currency and
|
||||
country of prices (optional, default: `de.farnell.com`,
|
||||
see [here](https://partner.element14.com/docs/Product_Search_API_REST__Description) for available values)
|
||||
|
||||
### Mouser
|
||||
|
||||
The Mouser provider uses the [Mouser API](https://www.mouser.de/api-home/) to search for parts and getting shopping
|
||||
information from [Mouser](https://www.mouser.com/).
|
||||
You have to create an account at Mouser and register for an API key for the Search API on
|
||||
the [Mouser API page](https://www.mouser.de/api-home/).
|
||||
You will receive an API token, which you have to put in the Part-DB env configuration (see below):
|
||||
At the registration you choose a country, language and currency in which you want to get the results.
|
||||
|
||||
Following env configuration options are available:
|
||||
|
||||
* `PROVIDER_MOUSER_KEY`: The API key you got from Mouser (mandatory)
|
||||
* `PROVIDER_MOUSER_SEARCH_LIMIT`: The maximum number of results to return per search (maximum 50)
|
||||
* `PROVIDER_MOUSER_SEARCH_OPTION`: You can choose an option here to restrict the search results to RoHs compliant and
|
||||
available parts. Possible values are `None`, `Rohs`, `InStock`, `RohsAndInStock`.
|
||||
* `PROVIDER_MOUSER_SEARCH_WITH_SIGNUP_LANGUAGE`: A bit of an obscure option. The original description of Mouser is: Used
|
||||
when searching for keywords in the language specified when you signed up for Search API.
|
||||
|
||||
### Custom provider
|
||||
To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long as it is a valid Symfony service, it will be automatically loaded and can be used.
|
||||
Besides some metadata functions, you have to implement the `searchByKeyword()` and `getDetails()` functions, which do the actual API requests and return the information to Part-DB.
|
||||
|
||||
To create a custom provider, you have to create a new class implementing the `InfoProviderInterface` interface. As long
|
||||
as it is a valid Symfony service, it will be automatically loaded and can be used.
|
||||
Besides some metadata functions, you have to implement the `searchByKeyword()` and `getDetails()` functions, which do
|
||||
the actual API requests and return the information to Part-DB.
|
||||
See the existing providers for examples.
|
||||
If you created a new provider, feel free to create a pull request to add it to the Part-DB core.
|
||||
|
||||
## Result caching
|
||||
|
||||
To reduce the number of API calls against the providers, the results are cached:
|
||||
|
||||
* The search results (exact search term) are cached for 7 days
|
||||
* The product details are cached for 4 days
|
||||
|
||||
If you need a fresh result, you can clear the cache by running `php .\bin\console cache:pool:clear info_provider.cache` on the command line.
|
||||
If you need a fresh result, you can clear the cache by running `php .\bin\console cache:pool:clear info_provider.cache`
|
||||
on the command line.
|
||||
The default `php bin/console cache:clear` also clears the result cache, as it clears all caches.
|
||||
|
||||
@@ -9,104 +9,107 @@ parent: Usage
|
||||
This page lists all the keybindings of Part-DB. Currently, there are only the special character keybindings.
|
||||
|
||||
## Special characters
|
||||
Using the keybindings below (Alt + key) you can insert special characters into the text fields of Part-DB. This works on all text and search fields in Part-DB.
|
||||
|
||||
Using the keybindings below (Alt + key) you can insert special characters into the text fields of Part-DB. This works on
|
||||
all text and search fields in Part-DB.
|
||||
|
||||
### Greek letters
|
||||
|
||||
| Key | Character |
|
||||
|---------------------|---------------------|
|
||||
| **Alt + a** | α (Alpha) |
|
||||
| **Alt + Shift + A** | Α (Alpha uppercase) |
|
||||
| **Alt + b** | β (Beta) |
|
||||
| **Alt + Shift + B** | Β (Beta uppercase) |
|
||||
| **Alt + g** | γ (Gamma) |
|
||||
| **Alt + Shift + G** | Γ (Gamma uppercase) |
|
||||
| **Alt + d** | δ (Delta) |
|
||||
| **Alt + Shift + D** | Δ (Delta uppercase) |
|
||||
| **Alt + e** | ε (Epsilon) |
|
||||
| Key | Character |
|
||||
|---------------------|-----------------------|
|
||||
| **Alt + a** | α (Alpha) |
|
||||
| **Alt + Shift + A** | Α (Alpha uppercase) |
|
||||
| **Alt + b** | β (Beta) |
|
||||
| **Alt + Shift + B** | Β (Beta uppercase) |
|
||||
| **Alt + g** | γ (Gamma) |
|
||||
| **Alt + Shift + G** | Γ (Gamma uppercase) |
|
||||
| **Alt + d** | δ (Delta) |
|
||||
| **Alt + Shift + D** | Δ (Delta uppercase) |
|
||||
| **Alt + e** | ε (Epsilon) |
|
||||
| **Alt + Shift + E** | Ε (Epsilon uppercase) |
|
||||
| **Alt + z** | ζ (Zeta) |
|
||||
| **Alt + Shift + Z** | Ζ (Zeta uppercase) |
|
||||
| **Alt + h** | η (Eta) |
|
||||
| **Alt + Shift + H** | Η (Eta uppercase) |
|
||||
| **Alt + q** | θ (Theta) |
|
||||
| **Alt + Shift + Q** | Θ (Theta uppercase) |
|
||||
| **Alt + i** | ι (Iota) |
|
||||
| **Alt + Shift + I** | Ι (Iota uppercase) |
|
||||
| **Alt + k** | κ (Kappa) |
|
||||
| **Alt + Shift + K** | Κ (Kappa uppercase) |
|
||||
| **Alt + l** | λ (Lambda) |
|
||||
| **Alt + Shift + L** | Λ (Lambda uppercase) |
|
||||
| **Alt + m** | μ (Mu) |
|
||||
| **Alt + Shift + M** | Μ (Mu uppercase) |
|
||||
| **Alt + n** | ν (Nu) |
|
||||
| **Alt + Shift + N** | Ν (Nu uppercase) |
|
||||
| **Alt + x** | ξ (Xi) |
|
||||
| **Alt + Shift + x** | Ξ (Xi uppercase) |
|
||||
| **Alt + o** | ο (Omicron) |
|
||||
| **Alt + z** | ζ (Zeta) |
|
||||
| **Alt + Shift + Z** | Ζ (Zeta uppercase) |
|
||||
| **Alt + h** | η (Eta) |
|
||||
| **Alt + Shift + H** | Η (Eta uppercase) |
|
||||
| **Alt + q** | θ (Theta) |
|
||||
| **Alt + Shift + Q** | Θ (Theta uppercase) |
|
||||
| **Alt + i** | ι (Iota) |
|
||||
| **Alt + Shift + I** | Ι (Iota uppercase) |
|
||||
| **Alt + k** | κ (Kappa) |
|
||||
| **Alt + Shift + K** | Κ (Kappa uppercase) |
|
||||
| **Alt + l** | λ (Lambda) |
|
||||
| **Alt + Shift + L** | Λ (Lambda uppercase) |
|
||||
| **Alt + m** | μ (Mu) |
|
||||
| **Alt + Shift + M** | Μ (Mu uppercase) |
|
||||
| **Alt + n** | ν (Nu) |
|
||||
| **Alt + Shift + N** | Ν (Nu uppercase) |
|
||||
| **Alt + x** | ξ (Xi) |
|
||||
| **Alt + Shift + x** | Ξ (Xi uppercase) |
|
||||
| **Alt + o** | ο (Omicron) |
|
||||
| **Alt + Shift + O** | Ο (Omicron uppercase) |
|
||||
| **Alt + p** | π (Pi) |
|
||||
| **Alt + Shift + P** | Π (Pi uppercase) |
|
||||
| **Alt + r** | ρ (Rho) |
|
||||
| **Alt + Shift + R** | Ρ (Rho uppercase) |
|
||||
| **Alt + s** | σ (Sigma) |
|
||||
| **Alt + Shift + S** | Σ (Sigma uppercase) |
|
||||
| **Alt + t** | τ (Tau) |
|
||||
| **Alt + Shift + T** | Τ (Tau uppercase) |
|
||||
| **Alt + u** | υ (Upsilon) |
|
||||
| **Alt + p** | π (Pi) |
|
||||
| **Alt + Shift + P** | Π (Pi uppercase) |
|
||||
| **Alt + r** | ρ (Rho) |
|
||||
| **Alt + Shift + R** | Ρ (Rho uppercase) |
|
||||
| **Alt + s** | σ (Sigma) |
|
||||
| **Alt + Shift + S** | Σ (Sigma uppercase) |
|
||||
| **Alt + t** | τ (Tau) |
|
||||
| **Alt + Shift + T** | Τ (Tau uppercase) |
|
||||
| **Alt + u** | υ (Upsilon) |
|
||||
| **Alt + Shift + U** | Υ (Upsilon uppercase) |
|
||||
| **Alt + f** | φ (Phi) |
|
||||
| **Alt + Shift + F** | Φ (Phi uppercase) |
|
||||
| **Alt + y** | ψ (Psi) |
|
||||
| **Alt + Shift + Y** | Ψ (Psi uppercase) |
|
||||
| **Alt + c** | χ (Chi) |
|
||||
| **Alt + Shift + C** | Χ (Chi uppercase) |
|
||||
| **Alt + w** | ω (Omega) |
|
||||
| **Alt + Shift + W** | Ω (Omega uppercase) |
|
||||
| **Alt + f** | φ (Phi) |
|
||||
| **Alt + Shift + F** | Φ (Phi uppercase) |
|
||||
| **Alt + y** | ψ (Psi) |
|
||||
| **Alt + Shift + Y** | Ψ (Psi uppercase) |
|
||||
| **Alt + c** | χ (Chi) |
|
||||
| **Alt + Shift + C** | Χ (Chi uppercase) |
|
||||
| **Alt + w** | ω (Omega) |
|
||||
| **Alt + Shift + W** | Ω (Omega uppercase) |
|
||||
|
||||
### Mathematical symbols
|
||||
|
||||
| Key | Character |
|
||||
|----------------------|-------------------------------------------|
|
||||
| **Alt + 1** | ∑ (Sum symbol) |
|
||||
| **Alt + Shift + 1** | ∏ (Product symbol) |
|
||||
| **Alt + 2** | ∫ (Integral symbol) |
|
||||
| **Alt + Shift + 2** | ∂ (Partial derivation) |
|
||||
| **Alt + 3** | ≤ (Less or equal symbol) |
|
||||
| **Alt + Shift + 3** | ≥ (Greater or equal symbol) |
|
||||
| **Alt + 4** | ∞ (Infinity symbol) |
|
||||
| **Alt + Shift + 4** | ∅ (Empty set symbol) |
|
||||
| **Alt + 5** | ≈ (Approximatley) |
|
||||
| **Alt + Shift + 5** | ≠ (Not equal symbol) |
|
||||
| **Alt + 6** | ∈ (Element of) |
|
||||
| **Alt + Shift + 6** | ∉ (Not element of) |
|
||||
| **Alt + 7** | ∨ (Logical or) |
|
||||
| **Alt + Shift + 7** | ∧ (Logical and) |
|
||||
| **Alt + 8** | ∠ (Angle symbol) |
|
||||
| **Alt + Shift + 8** | ∝ (Proportional to) |
|
||||
| **Alt + 9** | √ (Square root) |
|
||||
| **Alt + Shift + 9** | ∛ (Cube root) |
|
||||
| **Alt + 0** | ± (Plus minus) |
|
||||
| **Alt + Shift + 0** | ∓ (Minus plus) |
|
||||
| Key | Character |
|
||||
|---------------------|-----------------------------|
|
||||
| **Alt + 1** | ∑ (Sum symbol) |
|
||||
| **Alt + Shift + 1** | ∏ (Product symbol) |
|
||||
| **Alt + 2** | ∫ (Integral symbol) |
|
||||
| **Alt + Shift + 2** | ∂ (Partial derivation) |
|
||||
| **Alt + 3** | ≤ (Less or equal symbol) |
|
||||
| **Alt + Shift + 3** | ≥ (Greater or equal symbol) |
|
||||
| **Alt + 4** | ∞ (Infinity symbol) |
|
||||
| **Alt + Shift + 4** | ∅ (Empty set symbol) |
|
||||
| **Alt + 5** | ≈ (Approximately) |
|
||||
| **Alt + Shift + 5** | ≠ (Not equal symbol) |
|
||||
| **Alt + 6** | ∈ (Element of) |
|
||||
| **Alt + Shift + 6** | ∉ (Not element of) |
|
||||
| **Alt + 7** | ∨ (Logical or) |
|
||||
| **Alt + Shift + 7** | ∧ (Logical and) |
|
||||
| **Alt + 8** | ∠ (Angle symbol) |
|
||||
| **Alt + Shift + 8** | ∝ (Proportional to) |
|
||||
| **Alt + 9** | √ (Square root) |
|
||||
| **Alt + Shift + 9** | ∛ (Cube root) |
|
||||
| **Alt + 0** | ± (Plus minus) |
|
||||
| **Alt + Shift + 0** | ∓ (Minus plus) |
|
||||
|
||||
### Currency symbols
|
||||
|
||||
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all keyboards.
|
||||
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all
|
||||
keyboards.
|
||||
It is given here for a US keyboard layout.
|
||||
|
||||
For a German keyboard layout, replace ; with ö, and ' with ä.
|
||||
|
||||
| Key | Character |
|
||||
|---------------------------------|---------------------------|
|
||||
| **Alt + ;** (code 192) | € (Euro currency symbol) |
|
||||
| **Alt + Shift + ;** (code 192) | £ (Pound currency symbol) |
|
||||
| **Alt + '** (code 222) | ¥ (Yen currency symbol) |
|
||||
| Key | Character |
|
||||
|---------------------------------|----------------------------|
|
||||
| **Alt + ;** (code 192) | € (Euro currency symbol) |
|
||||
| **Alt + Shift + ;** (code 192) | £ (Pound currency symbol) |
|
||||
| **Alt + '** (code 222) | ¥ (Yen currency symbol) |
|
||||
| **Alt + Shift + '** (code 222) | $ (Dollar currency symbol) |
|
||||
|
||||
|
||||
### Others
|
||||
|
||||
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all keyboards.
|
||||
Please not the following keybindings are bound to a specific keycode. The key character is not the same on all
|
||||
keyboards.
|
||||
It is given here for a US keyboard layout.
|
||||
|
||||
For a German keyboard layout, replace `[` with `0`, and `]` with `´`.
|
||||
@@ -114,6 +117,6 @@ For a German keyboard layout, replace `[` with `0`, and `]` with `´`.
|
||||
| Key | Character |
|
||||
|--------------------------------|--------------------|
|
||||
| **Alt + [** (code 219) | © (Copyright char) |
|
||||
| **Alt + Shift + [** (code 219) | (Registered char) |
|
||||
| **Alt + Shift + [** (code 219) | (Registered char) |
|
||||
| **Alt + ]** (code 221) | ™ (Trademark char) |
|
||||
| **Alt + Shift + ]** (code 221) | (Degree char) |
|
||||
| **Alt + Shift + ]** (code 221) | (Degree char) |
|
||||
|
||||
@@ -6,103 +6,114 @@ parent: Usage
|
||||
|
||||
# Labels
|
||||
|
||||
Part-DB support the generation and printing of labels for parts, part lots and storelocation.
|
||||
You can use the "Tools -> Labelgenerator" menu entry to create labels, or click the label generation link on the part.
|
||||
Part-DB support the generation and printing of labels for parts, part lots and storage locations.
|
||||
You can use the "Tools -> Label generator" menu entry to create labels, or click the label generation link on the part.
|
||||
|
||||
You can define label templates by creating Label profiles. This way you can create many similar looking labels with for
|
||||
many parts.
|
||||
|
||||
The content of the labels is defined by the templates content field. You can use the WYSIWYG editor to create and style the content (or write HTML code).
|
||||
The content of the labels is defined by the templates content field. You can use the WYSIWYG editor to create and style
|
||||
the content (or write HTML code).
|
||||
Using the "Label placeholder" menu in the editor, you can insert placeholders for the data of the parts.
|
||||
It will be replaced by the concrete data when the label is generated.
|
||||
|
||||
|
||||
## Label placeholders
|
||||
|
||||
A placeholder has the format `[[PLACEHOLDER]]` and will be filled with the concrete data by Part-DB.
|
||||
You can use the "Placeholders" dropdown in content editor, to automatically insert the placeholders.
|
||||
|
||||
### Common
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|---|---|---|
|
||||
| `[[USERNAME]]` | The user name of the currently logged in user | admin |
|
||||
| `[[USERNAME_FULL]]` | The full name of the current user | John Doe (@admin) |
|
||||
| `[[DATETIME]]` | The current date and time in the selected locale | 31.12.2017, 18:34:11 |
|
||||
| `[[DATE]]` | The current date in the selected locale | 31.12.2017 |
|
||||
| `[[TIME]]` | The current time in the selected locale | 18:34:11 |
|
||||
| `[[INSTALL_NAME]]` | The name of the current installation (see $config['partdb_title']) | Part-DB |
|
||||
| `[[INSTANCE_URL]]` | The URL of the current installation | https://demo.part-db.de |
|
||||
| Placeholder | Description | Example |
|
||||
|---------------------|--------------------------------------------------------------------|-------------------------|
|
||||
| `[[USERNAME]]` | The user name of the currently logged in user | admin |
|
||||
| `[[USERNAME_FULL]]` | The full name of the current user | John Doe (@admin) |
|
||||
| `[[DATETIME]]` | The current date and time in the selected locale | 31.12.2017, 18:34:11 |
|
||||
| `[[DATE]]` | The current date in the selected locale | 31.12.2017 |
|
||||
| `[[TIME]]` | The current time in the selected locale | 18:34:11 |
|
||||
| `[[INSTALL_NAME]]` | The name of the current installation (see $config['partdb_title']) | Part-DB |
|
||||
| `[[INSTANCE_URL]]` | The URL of the current installation | https://demo.part-db.de |
|
||||
|
||||
### Parts
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|---|---|---|
|
||||
| `[[ID]]` | The internal ID of the part | 24 |
|
||||
| `[[NAME]]` | The name of the part | ATMega328 |
|
||||
| `[[CATEGORY]]` | The name of the category (without path) | AVRs |
|
||||
| `[[CATEGORY_FULL]]` | The full path of the category | Aktiv->MCUs->AVRs |
|
||||
| `[[MANUFACTURER]]` | The name of the manufacturer | Atmel |
|
||||
| `[[MANUFACTURER_FULL]]` | The full path of the manufacturer | Halbleiterhersteller->Atmel |
|
||||
| `[[FOOTPRINT]]` | The name of the footprint (without path) | DIP-32 |
|
||||
| `[[FOOTPRINT_FULL]]` | The full path of the footprint | Bedrahtet->DIP->DIP-32 |
|
||||
| `[[MASS]]` | The mass of the part | 123.4 g |
|
||||
| `[[MPN]]` | The manufacturer product number | BC547ACT |
|
||||
| `[[TAGS]]` | The tags of the part | SMD, Tag1 |
|
||||
| `[[M_STATUS]]` | The manufacturing status of the part | Active |
|
||||
| `[[DESCRIPTION]]` | The rich text description of the part | *NPN* |
|
||||
| `[[DESCRIPTION_T]]` | The description as plain text | NPN |
|
||||
| `[[COMMENT]]` | The rich text comment of the part | |
|
||||
| `[[COMMENT_T]]` | The comment as plain text | |
|
||||
| `[[LAST_MODIFIED]]` | The datetime when the element was last modified | 2/26/16, 5:38 PM |
|
||||
| `[[CREATION_DATE]]` | The datetime when the element was created | 2/26/16, 5:38 PM |
|
||||
| Placeholder | Description | Example |
|
||||
|-------------------------|-------------------------------------------------|-----------------------------|
|
||||
| `[[ID]]` | The internal ID of the part | 24 |
|
||||
| `[[NAME]]` | The name of the part | ATMega328 |
|
||||
| `[[CATEGORY]]` | The name of the category (without path) | AVRs |
|
||||
| `[[CATEGORY_FULL]]` | The full path of the category | Aktiv->MCUs->AVRs |
|
||||
| `[[MANUFACTURER]]` | The name of the manufacturer | Atmel |
|
||||
| `[[MANUFACTURER_FULL]]` | The full path of the manufacturer | Halbleiterhersteller->Atmel |
|
||||
| `[[FOOTPRINT]]` | The name of the footprint (without path) | DIP-32 |
|
||||
| `[[FOOTPRINT_FULL]]` | The full path of the footprint | Bedrahtet->DIP->DIP-32 |
|
||||
| `[[MASS]]` | The mass of the part | 123.4 g |
|
||||
| `[[MPN]]` | The manufacturer product number | BC547ACT |
|
||||
| `[[TAGS]]` | The tags of the part | SMD, Tag1 |
|
||||
| `[[M_STATUS]]` | The manufacturing status of the part | Active |
|
||||
| `[[DESCRIPTION]]` | The rich text description of the part | *NPN* |
|
||||
| `[[DESCRIPTION_T]]` | The description as plain text | NPN |
|
||||
| `[[COMMENT]]` | The rich text comment of the part | |
|
||||
| `[[COMMENT_T]]` | The comment as plain text | |
|
||||
| `[[LAST_MODIFIED]]` | The datetime when the element was last modified | 2/26/16, 5:38 PM |
|
||||
| `[[CREATION_DATE]]` | The datetime when the element was created | 2/26/16, 5:38 PM |
|
||||
|
||||
### Part lot
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|---|---|---|
|
||||
| `[[LOT_ID]]` | Part lot ID | 123 |
|
||||
| `[[LOT_NAME]]` | Part lot name | |
|
||||
| `[[LOT_COMMENT]]` | Part lot comment | |
|
||||
| `[[EXPIRATION_DATE]]` | Expiration date of the part lot | |
|
||||
| `[[AMOUNT]]` | The amount of parts in this lot | 12 |
|
||||
| `[[LOCATION]]` | The storage location of this part lot | Location A |
|
||||
| `[[LOCATION_FULL]]` | The full path of the storage location | Location -> Location A |
|
||||
| Placeholder | Description | Example |
|
||||
|-----------------------|---------------------------------------|------------------------|
|
||||
| `[[LOT_ID]]` | Part lot ID | 123 |
|
||||
| `[[LOT_NAME]]` | Part lot name | |
|
||||
| `[[LOT_COMMENT]]` | Part lot comment | |
|
||||
| `[[EXPIRATION_DATE]]` | Expiration date of the part lot | |
|
||||
| `[[AMOUNT]]` | The amount of parts in this lot | 12 |
|
||||
| `[[LOCATION]]` | The storage location of this part lot | Location A |
|
||||
| `[[LOCATION_FULL]]` | The full path of the storage location | Location -> Location A |
|
||||
|
||||
### Storelocation
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|---|---|---|
|
||||
| `[[ID]]` | ID of the storage location | |
|
||||
| `[[NAME]]` | Name of the storage location | Location A |
|
||||
| `[[FULL_PATH]]` | The full path of the storage location | Location -> Location A |
|
||||
| `[[PARENT]]` | The name of the parent location | Location |
|
||||
| `[[PARENT_FULL_PATH]]` | The full path of the storage location | |
|
||||
| `[[COMMENT]]` | The comment of the storage location | |
|
||||
| `[[COMMENT_T]]` | The plain text version of the comment |
|
||||
| `[[LAST_MODIFIED]]` | The datetime when the element was last modified | 2/26/16, 5:38 PM |
|
||||
| `[[CREATION_DATE]]` | The datetime when the element was created | 2/26/16, 5:38 PM |
|
||||
| Placeholder | Description | Example |
|
||||
|------------------------|-------------------------------------------------|------------------------|
|
||||
| `[[ID]]` | ID of the storage location | |
|
||||
| `[[NAME]]` | Name of the storage location | Location A |
|
||||
| `[[FULL_PATH]]` | The full path of the storage location | Location -> Location A |
|
||||
| `[[PARENT]]` | The name of the parent location | Location |
|
||||
| `[[PARENT_FULL_PATH]]` | The full path of the storage location | |
|
||||
| `[[COMMENT]]` | The comment of the storage location | |
|
||||
| `[[COMMENT_T]]` | The plain text version of the comment |
|
||||
| `[[LAST_MODIFIED]]` | The datetime when the element was last modified | 2/26/16, 5:38 PM |
|
||||
| `[[CREATION_DATE]]` | The datetime when the element was created | 2/26/16, 5:38 PM |
|
||||
|
||||
## Twig mode
|
||||
|
||||
If you select "Twig" in parser mode under advanced settings, you can input a twig template in the lines field (activate source mode). You can use most of the twig tags and filters listed in [offical documentation](https://twig.symfony.com/doc/3.x/).
|
||||
If you select "Twig" in parser mode under advanced settings, you can input a twig template in the lines field (activate
|
||||
source mode). You can use most of the twig tags and filters listed
|
||||
in [official documentation](https://twig.symfony.com/doc/3.x/).
|
||||
|
||||
The following variables are in injected into Twig and can be accessed using `{% raw %}{{ variable }}` (or `{% raw %}{{ variable.property }}{% endraw %}`):
|
||||
|
||||
| Variable name | Description |
|
||||
|--------------------------------------| ----------- |
|
||||
| `{% raw %}{{ element }}{% endraw %}` | The target element, selected in label dialog |
|
||||
| `{% raw %}{{ user }}{% endraw %}` | The current logged in user. Null if you are not logged in |
|
||||
| `{% raw %}{{ install_title }}{% endraw %}` | The name of the current Part-DB instance (similar to [[INSTALL_NAME]] placeholder). |
|
||||
| `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated |
|
||||
The following variables are in injected into Twig and can be accessed using `{% raw %}{{ variable }}` (
|
||||
or `{% raw %}{{ variable.property }}{% endraw %}`):
|
||||
|
||||
| Variable name | Description |
|
||||
|--------------------------------------------|-------------------------------------------------------------------------------------|
|
||||
| `{% raw %}{{ element }}{% endraw %}` | The target element, selected in label dialog |
|
||||
| `{% raw %}{{ user }}{% endraw %}` | The current logged in user. Null if you are not logged in |
|
||||
| `{% raw %}{{ install_title }}{% endraw %}` | The name of the current Part-DB instance (similar to [[INSTALL_NAME]] placeholder). |
|
||||
| `{% raw %}{{ page }}{% endraw %}` | The page number (the nth-element for which the label is generated |
|
||||
|
||||
## Use custom fonts for PDF labels
|
||||
You can use your own fonts for label generation. To do this, put the TTF files of the fonts you want to use into the `assets/fonts/dompdf` folder.
|
||||
The filename will be used as name for the font family and you can use a `_bold` (or `_b`), `_italic` (or `_i`) or `_bold_italic` (or `_bi`) suffix to define
|
||||
different styles of the font. So for example, if you copy the file `myfont.ttf` and `myfont_bold.ttf` into the `assets/fonts/dompdf` folder, you can use the font family `myfont` with regular and bold style.
|
||||
Afterwards regenerate cache with `php bin/console cache:clear`, so the new fonts will be available for label generation.
|
||||
|
||||
The fonts will not be availble from the UI directly, you have to use it in the HTML directly either by defining a `style="font-family: 'myfont';"` attribute on the HTML element or by using a CSS class.
|
||||
You can define the font globally for the label, by adding following statement to the "Additional styles (CSS)" option in the label generator settings:
|
||||
You can use your own fonts for label generation. To do this, put the TTF files of the fonts you want to use into
|
||||
the `assets/fonts/dompdf` folder.
|
||||
The filename will be used as name for the font family, and you can use a `_bold` (or `_b`), `_italic` (or `_i`)
|
||||
or `_bold_italic` (or `_bi`) suffix to define
|
||||
different styles of the font. So for example, if you copy the file `myfont.ttf` and `myfont_bold.ttf` into
|
||||
the `assets/fonts/dompdf` folder, you can use the font family `myfont` with regular and bold style.
|
||||
Afterward regenerate cache with `php bin/console cache:clear`, so the new fonts will be available for label generation.
|
||||
|
||||
The fonts will not be available from the UI directly, you have to use it in the HTML directly either by defining
|
||||
a `style="font-family: 'myfont';"` attribute on the HTML element or by using a CSS class.
|
||||
You can define the font globally for the label, by adding following statement to the "Additional styles (CSS)" option in
|
||||
the label generator settings:
|
||||
|
||||
```css
|
||||
* {
|
||||
font-family: 'myfont';
|
||||
@@ -110,9 +121,15 @@ You can define the font globally for the label, by adding following statement to
|
||||
```
|
||||
|
||||
## Non-latin characters in PDF labels
|
||||
The default used font (DejaVu) does not support all characters. Especially characters from non-latin languages like Chinese, Japanese, Korean, Arabic, Hebrew, Cyrillic, etc. are not supported.
|
||||
For this we use [Unifont](http://unifoundry.com/unifont.html) as fallback font. This font supports all (or most) unicode characters, but is not as beautiful as DejaVu.
|
||||
|
||||
If you want to use a different (more beautiful) font, you can use the [custom fonts](#use-custom-fonts-for-pdf-labels) feature.
|
||||
There is the [Noto](https://www.google.com/get/noto/) font family from Google, which supports a lot of languages and is available in different styles (regular, bold, italic, bold-italic).
|
||||
For example you can use [Noto CJK](https://github.com/notofonts/noto-cjk) for more beautful Chinese, Japanese and Korean characters.
|
||||
The default used font (DejaVu) does not support all characters. Especially characters from non-latin languages like
|
||||
Chinese, Japanese, Korean, Arabic, Hebrew, Cyrillic, etc. are not supported.
|
||||
For this we use [Unifont](http://unifoundry.com/unifont.html) as fallback font. This font supports all (or most) unicode
|
||||
characters, but is not as beautiful as DejaVu.
|
||||
|
||||
If you want to use a different (more beautiful) font, you can use the [custom fonts](#use-custom-fonts-for-pdf-labels)
|
||||
feature.
|
||||
There is the [Noto](https://www.google.com/get/noto/) font family from Google, which supports a lot of languages and is
|
||||
available in different styles (regular, bold, italic, bold-italic).
|
||||
For example, you can use [Noto CJK](https://github.com/notofonts/noto-cjk) for more beautiful Chinese, Japanese
|
||||
and Korean characters.
|
||||
@@ -10,39 +10,48 @@ Following you can find miscellaneous tips and tricks for using Part-DB.
|
||||
|
||||
## Create datastructures directly from part edit page
|
||||
|
||||
Instead of first creating a category, manufacturer, footprint, etc. and then creating the part, you can create the
|
||||
datastructures directly from the part edit page: Just type the name of the datastructure you want to create into the
|
||||
Instead of first creating a category, manufacturer, footprint, etc. and then creating the part, you can create the
|
||||
datastructures directly from the part edit page: Just type the name of the datastructure you want to create into the
|
||||
select field on the part edit page and press "Create new ...". The new datastructure will be created, when you save
|
||||
the part changes.
|
||||
|
||||
You can create also create nested datastructures this way. For example, if you want to create a new category "AVRs",
|
||||
You can create also create nested datastructures this way. For example, if you want to create a new category "AVRs",
|
||||
as a subcategory of "MCUs", you can just type "MCUs->AVRs" into the category select field and press "Create new".
|
||||
The new category "AVRs" will be created as a subcategory of "MCUs". If the category "MCUs" does not exist, it will
|
||||
be created too.
|
||||
|
||||
## Builtin footprint images
|
||||
Part-DB includes several builtin images for common footprints. You can use these images in your footprint datastructures,
|
||||
|
||||
Part-DB includes several builtin images for common footprints. You can use these images in your footprint
|
||||
datastructures,
|
||||
by creating an attachment on the datastructure and selecting it as preview image.
|
||||
Type the name of the footprint image you want to use into the URL field of the attachment and select it from the
|
||||
dropdown menu. You can find a gallery of all builtin footprint images and their names in the "Builtin footprint image gallery",
|
||||
dropdown menu. You can find a gallery of all builtin footprint images and their names in the "Builtin footprint image
|
||||
gallery",
|
||||
which you can find in the "Tools" menu (you maybe need to give your user the permission to access this tool).
|
||||
|
||||
## Parametric search
|
||||
|
||||
In the "parameters" tab of the filter panel on parts list page, you can define constraints, which parameter values
|
||||
have to fullfill. This allows you to search for parts with specific parameters (or parameter ranges), for example you
|
||||
have to fulfill. This allows you to search for parts with specific parameters (or parameter ranges), for example you
|
||||
can search for all parts with a voltage rating of greater than 5 V.
|
||||
|
||||
## View own users permissions
|
||||
|
||||
If you want to see which permissions your user has, you can find a list of the permissions in the "Permissions" panel
|
||||
on the user info page.
|
||||
|
||||
## Use LaTeX equations
|
||||
You can use LaTeX equations everywhere where markdown is supported (for example in the description or notes field of a part).
|
||||
|
||||
You can use LaTeX equations everywhere where markdown is supported (for example in the description or notes field of a
|
||||
part).
|
||||
[KaTeX](https://katex.org/) is used to render the equations.
|
||||
You can find a list of supported features in the [KaTeX documentation](https://katex.org/docs/supported.html).
|
||||
|
||||
To input a LaTeX equation, you have to wrap it in a pair of dollar signs (`$`). Single dollar signs mark inline equations,
|
||||
double dollar signs mark displayed equations (which will be its own line and centered). For example, the following equation
|
||||
To input a LaTeX equation, you have to wrap it in a pair of dollar signs (`$`). Single dollar signs mark inline
|
||||
equations,
|
||||
double dollar signs mark displayed equations (which will be its own line and centered). For example, the following
|
||||
equation
|
||||
will be rendered as an inline equation:
|
||||
|
||||
```
|
||||
@@ -56,7 +65,8 @@ $$E=mc^2$$
|
||||
```
|
||||
|
||||
## Update currency exchange rates automatically
|
||||
Part-DB can update the currency exchange rates of all defined currencies programatically
|
||||
|
||||
Part-DB can update the currency exchange rates of all defined currencies programmatically
|
||||
by calling the `php bin/console partdb:currencies:update-exchange-rates`.
|
||||
|
||||
If you call this command regularly (e.g. with a cronjob), you can keep the exchange rates up-to-date.
|
||||
@@ -65,18 +75,26 @@ Please note that if you use a base currency, which is not the Euro, you have to
|
||||
free API used by default only supports the Euro as base currency.
|
||||
|
||||
## Enforce log comments
|
||||
|
||||
On almost any editing operation it is possible to add a comment describing, what or why you changed something.
|
||||
This comment will be written to change log and can be viewed later.
|
||||
If you want to enforce your users to add comments to certain operations, you can do this by setting the `ENFORCE_CHANGE_COMMENTS_FOR` option.
|
||||
If you want to enforce your users to add comments to certain operations, you can do this by setting
|
||||
the `ENFORCE_CHANGE_COMMENTS_FOR` option.
|
||||
See the configuration reference for more information.
|
||||
|
||||
## Personal stocks and stock locations
|
||||
For makerspaces and universities with a lot of users, where each user can have his own stock, which only he should be able to access, you can assign
|
||||
|
||||
For makerspaces and universities with a lot of users, where each user can have his own stock, which only he should be
|
||||
able to access, you can assign
|
||||
the user as "owner" of a part lot. This way, only him is allowed to add or remove parts from this lot.
|
||||
|
||||
## Update notfications
|
||||
Part-DB can show you a notification that there is a newer version than currently installed available. The notification will be shown on the homepage and the server info page.
|
||||
## Update notifications
|
||||
|
||||
Part-DB can show you a notification that there is a newer version than currently installed available. The notification
|
||||
will be shown on the homepage and the server info page.
|
||||
It is only be shown to users which has the `Show available Part-DB updates` permission.
|
||||
|
||||
For the notification to work, Part-DB queries the GitHub API every 2 days to check for new releases. No data is sent to GitHub besides the metadata required for the connection (so the public IP address of your computer running Part-DB).
|
||||
If you don't want Part-DB to query the GitHub API, or if your server can not reach the internet, you can disable the update notifications by setting the `CHECK_FOR_UPDATES` option to `false`.
|
||||
For the notification to work, Part-DB queries the GitHub API every 2 days to check for new releases. No data is sent to
|
||||
GitHub besides the metadata required for the connection (so the public IP address of your computer running Part-DB).
|
||||
If you don't want Part-DB to query the GitHub API, or if your server can not reach the internet, you can disable the
|
||||
update notifications by setting the `CHECK_FOR_UPDATES` option to `false`.
|
||||
@@ -160,7 +160,7 @@ EOD;
|
||||
21840,21840,21840,21840,21840,21520,21520,21520,20480,21520,20480,
|
||||
20480,20480,20480,20480,21504,20480),
|
||||
(
|
||||
2,'admin', '${admin_pw}','','',
|
||||
2,'admin', '$admin_pw','','',
|
||||
'','',1,1,21845,21845,21845,21,85,21,349525,21845,21845,21845,21845
|
||||
,21845,21845,21845,21845,21845,21845,21845,21845,21845,21845,21845,
|
||||
21845,21845,21845,21845,21845,21845);
|
||||
|
||||
@@ -234,8 +234,8 @@ final class Version20190902140506 extends AbstractMultiPlatformMigration
|
||||
'orderdetails', 'pricedetails', 'storelocations', 'suppliers', ];
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$this->addSql("UPDATE ${table} SET datetime_added = NOW() WHERE datetime_added = '0000-00-00 00:00:00'");
|
||||
$this->addSql("UPDATE ${table} SET last_modified = datetime_added WHERE last_modified = '0000-00-00 00:00:00'");
|
||||
$this->addSql("UPDATE $table SET datetime_added = NOW() WHERE datetime_added = '0000-00-00 00:00:00'");
|
||||
$this->addSql("UPDATE $table SET last_modified = datetime_added WHERE last_modified = '0000-00-00 00:00:00'");
|
||||
}
|
||||
|
||||
//Set the dbVersion to a high value, to prevent the old Part-DB versions to upgrade DB!
|
||||
|
||||
@@ -83,9 +83,12 @@ final class Version20221114193325 extends AbstractMultiPlatformMigration impleme
|
||||
//Reset the permissions of the admin user, to allow admin permissions (like the admins group)
|
||||
$this->addSql("UPDATE `users` SET permissions_data = '$admin' WHERE id = 2;");
|
||||
|
||||
//This warning should not be needed, anymore, as almost everybody should have updated to the new version by now, and this warning would just irritate new users of the software
|
||||
/*
|
||||
$this->logger->warning('<bg=cyan;fg=black>!!! All permissions were reset! Please change them to the desired state, immediately !!!</>');
|
||||
$this->logger->warning('<bg=cyan;fg=black>!!! For security reasons all users (except the admin user) were disabled. Login with admin user and reenable other users after checking their permissions !!!</>');
|
||||
$this->logger->warning('<bg=cyan;fg=black>!!! For more infos see: https://github.com/Part-DB/Part-DB-symfony/discussions/193 !!!</>');
|
||||
*/
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
|
||||
44
migrations/Version20230816213201.php
Normal file
44
migrations/Version20230816213201.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20230816213201 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add table for API tokens';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE api_tokens (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until DATETIME DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, UNIQUE INDEX UNIQ_2CAD560E5F37A13B (token), INDEX IDX_2CAD560EA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE api_tokens ADD CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES `users` (id)');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE api_tokens DROP FOREIGN KEY FK_2CAD560EA76ED395');
|
||||
$this->addSql('DROP TABLE api_tokens');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE api_tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, name VARCHAR(255) NOT NULL, valid_until DATETIME DEFAULT NULL, token VARCHAR(68) NOT NULL, level SMALLINT NOT NULL, last_time_used DATETIME DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_2CAD560EA76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_2CAD560E5F37A13B ON api_tokens (token)');
|
||||
$this->addSql('CREATE INDEX IDX_2CAD560EA76ED395 ON api_tokens (user_id)');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE api_tokens');
|
||||
}
|
||||
}
|
||||
71
migrations/Version20231114223101.php
Normal file
71
migrations/Version20231114223101.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20231114223101 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add schema for part associations and vendor barcodes';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE part_association (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, other_id INT NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment LONGTEXT DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, INDEX IDX_61B952E07E3C61F9 (owner_id), INDEX IDX_61B952E0998D9879 (other_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||
$this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E07E3C61F9 FOREIGN KEY (owner_id) REFERENCES `parts` (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE part_association ADD CONSTRAINT FK_61B952E0998D9879 FOREIGN KEY (other_id) REFERENCES `parts` (id) ON DELETE CASCADE');
|
||||
$this->addSql('ALTER TABLE part_lots ADD vendor_barcode VARCHAR(255) DEFAULT NULL');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE part_association DROP FOREIGN KEY FK_61B952E07E3C61F9');
|
||||
$this->addSql('ALTER TABLE part_association DROP FOREIGN KEY FK_61B952E0998D9879');
|
||||
$this->addSql('DROP TABLE part_association');
|
||||
$this->addSql('DROP INDEX part_lots_idx_barcode ON part_lots');
|
||||
$this->addSql('ALTER TABLE part_lots DROP vendor_barcode');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE part_association (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, owner_id INTEGER NOT NULL, other_id INTEGER NOT NULL, type SMALLINT NOT NULL, other_type VARCHAR(255) DEFAULT NULL, comment CLOB DEFAULT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_61B952E07E3C61F9 FOREIGN KEY (owner_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_61B952E0998D9879 FOREIGN KEY (other_id) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('CREATE INDEX IDX_61B952E07E3C61F9 ON part_association (owner_id)');
|
||||
$this->addSql('CREATE INDEX IDX_61B952E0998D9879 ON part_association (other_id)');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__part_lots AS SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM part_lots');
|
||||
$this->addSql('DROP TABLE part_lots');
|
||||
$this->addSql('CREATE TABLE part_lots (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_store_location INTEGER DEFAULT NULL, id_part INTEGER NOT NULL, id_owner INTEGER DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, vendor_barcode VARCHAR(255) DEFAULT NULL, CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES storelocations (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES parts (id) ON UPDATE NO ACTION ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO part_lots (id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added) SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM __temp__part_lots');
|
||||
$this->addSql('DROP TABLE __temp__part_lots');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_barcode ON part_lots (vendor_barcode)');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE part_association');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__part_lots AS SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM part_lots');
|
||||
$this->addSql('DROP TABLE part_lots');
|
||||
$this->addSql('CREATE TABLE part_lots (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, id_store_location INTEGER DEFAULT NULL, id_part INTEGER NOT NULL, id_owner INTEGER DEFAULT NULL, description CLOB NOT NULL, comment CLOB NOT NULL, expiration_date DATETIME DEFAULT NULL, instock_unknown BOOLEAN NOT NULL, amount DOUBLE PRECISION NOT NULL, needs_refill BOOLEAN NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_EBC8F9435D8F4B37 FOREIGN KEY (id_store_location) REFERENCES "storelocations" (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F943C22F6CC4 FOREIGN KEY (id_part) REFERENCES "parts" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_EBC8F94321E5A74C FOREIGN KEY (id_owner) REFERENCES "users" (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO part_lots (id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added) SELECT id, id_store_location, id_part, id_owner, description, comment, expiration_date, instock_unknown, amount, needs_refill, last_modified, datetime_added FROM __temp__part_lots');
|
||||
$this->addSql('DROP TABLE __temp__part_lots');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F9435D8F4B37 ON part_lots (id_store_location)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F943C22F6CC4 ON part_lots (id_part)');
|
||||
$this->addSql('CREATE INDEX IDX_EBC8F94321E5A74C ON part_lots (id_owner)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_instock_un_expiration_id_part ON part_lots (instock_unknown, expiration_date, id_part)');
|
||||
$this->addSql('CREATE INDEX part_lots_idx_needs_refill ON part_lots (needs_refill)');
|
||||
}
|
||||
}
|
||||
66
package.json
66
package.json
@@ -30,38 +30,38 @@
|
||||
"build": "encore production --progress"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ckeditor/ckeditor5-alignment": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-autoformat": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-basic-styles": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-block-quote": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-code-block": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-dev-translations": "^38.4.0",
|
||||
"@ckeditor/ckeditor5-dev-utils": "^38.4.0",
|
||||
"@ckeditor/ckeditor5-editor-classic": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-essentials": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-find-and-replace": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-font": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-heading": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-highlight": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-horizontal-line": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-html-embed": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-html-support": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-image": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-indent": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-link": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-list": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-markdown-gfm": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-media-embed": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-paragraph": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-paste-from-office": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-remove-format": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-source-editing": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-special-characters": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-table": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-theme-lark": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-upload": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-watchdog": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-word-count": "^39.0.1",
|
||||
"@ckeditor/ckeditor5-alignment": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-autoformat": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-basic-styles": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-block-quote": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-code-block": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-dev-translations": "^39.1.0",
|
||||
"@ckeditor/ckeditor5-dev-utils": "^39.1.0",
|
||||
"@ckeditor/ckeditor5-editor-classic": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-essentials": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-find-and-replace": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-font": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-heading": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-highlight": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-horizontal-line": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-html-embed": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-html-support": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-image": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-indent": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-link": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-list": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-markdown-gfm": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-media-embed": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-paragraph": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-paste-from-office": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-remove-format": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-source-editing": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-special-characters": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-table": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-theme-lark": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-upload": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-watchdog": "^40.0.0",
|
||||
"@ckeditor/ckeditor5-word-count": "^40.0.0",
|
||||
"@jbtronics/bs-treeview": "^1.0.1",
|
||||
"@zxcvbn-ts/core": "^3.0.2",
|
||||
"@zxcvbn-ts/language-common": "^3.0.3",
|
||||
@@ -87,7 +87,7 @@
|
||||
"json-formatter-js": "^2.3.4",
|
||||
"jszip": "^3.2.0",
|
||||
"katex": "^0.16.0",
|
||||
"marked": "^7.0.4",
|
||||
"marked": "^9.1.0",
|
||||
"marked-gfm-heading-id": "^3.0.4",
|
||||
"marked-mangle": "^1.0.1",
|
||||
"pdfmake": "^0.2.2",
|
||||
|
||||
@@ -10,6 +10,7 @@ parameters:
|
||||
- src/DataTables/Adapter/*
|
||||
- src/Configuration/*
|
||||
- src/Doctrine/Purger/*
|
||||
- src/DataTables/Adapters/TwoStepORMAdapter.php
|
||||
|
||||
|
||||
|
||||
@@ -19,9 +20,13 @@ parameters:
|
||||
symfony:
|
||||
container_xml_path: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml'
|
||||
|
||||
doctrine:
|
||||
objectManagerLoader: tests/object-manager.php
|
||||
allowNullablePropertyForRequiredField: true
|
||||
|
||||
checkUninitializedProperties: true
|
||||
|
||||
checkFunctionNameCase: true
|
||||
checkFunctionNameCase: false
|
||||
|
||||
checkAlwaysTrueInstanceof: false
|
||||
checkAlwaysTrueCheckTypeFunctionCall: false
|
||||
@@ -48,8 +53,11 @@ parameters:
|
||||
ignoreErrors:
|
||||
# Ignore errors caused by complex mapping with AbstractStructuralDBElement
|
||||
- '#AbstractStructuralDBElement does not have a field named \$parent#'
|
||||
- '#AbstractStructuralDBElement does not have a field named \$name#'
|
||||
#- '#AbstractStructuralDBElement does not have a field named \$name#'
|
||||
|
||||
# Ignore errors related to the use of the ParametersTrait in Part entity
|
||||
- '#expects .*PartParameter, .*AbstractParameter given.#'
|
||||
- '#Part::getParameters\(\) should return .*AbstractParameter#'
|
||||
- '#Part::getParameters\(\) should return .*AbstractParameter#'
|
||||
|
||||
# Ignore doctrine type mapping mismatch
|
||||
- '#Property .* type mapping mismatch: property can contain .* but database expects .*#'
|
||||
119
src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php
Normal file
119
src/ApiPlatform/AddDocumentedAPIPropertiesJSONSchemaFactory.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform;
|
||||
|
||||
use ApiPlatform\JsonSchema\Schema;
|
||||
use ApiPlatform\JsonSchema\SchemaFactoryInterface;
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
/**
|
||||
* This decorator adds the properties given by DocumentedAPIProperty attributes on the classes to the schema.
|
||||
*/
|
||||
#[AsDecorator('api_platform.json_schema.schema_factory')]
|
||||
class AddDocumentedAPIPropertiesJSONSchemaFactory implements SchemaFactoryInterface
|
||||
{
|
||||
|
||||
public function __construct(private readonly SchemaFactoryInterface $decorated)
|
||||
{
|
||||
}
|
||||
|
||||
public function buildSchema(
|
||||
string $className,
|
||||
string $format = 'json',
|
||||
string $type = Schema::TYPE_OUTPUT,
|
||||
Operation $operation = null,
|
||||
Schema $schema = null,
|
||||
array $serializerContext = null,
|
||||
bool $forceCollection = false
|
||||
): Schema {
|
||||
|
||||
|
||||
$schema = $this->decorated->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection);
|
||||
|
||||
//Check if there is are DocumentedAPIProperty attributes on the class
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
$attributes = $reflectionClass->getAttributes(DocumentedAPIProperty::class);
|
||||
foreach ($attributes as $attribute) {
|
||||
/** @var DocumentedAPIProperty $api_property */
|
||||
$api_property = $attribute->newInstance();
|
||||
$this->addPropertyToSchema($schema, $api_property->schemaName, $api_property->property,
|
||||
$api_property, $serializerContext ?? [], $format);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
private function addPropertyToSchema(Schema $schema, string $definitionName, string $normalizedPropertyName, DocumentedAPIProperty $propertyMetadata, array $serializerContext, string $format): void
|
||||
{
|
||||
$version = $schema->getVersion();
|
||||
$swagger = Schema::VERSION_SWAGGER === $version;
|
||||
|
||||
$propertySchema = [];
|
||||
|
||||
if (false === $propertyMetadata->writeable) {
|
||||
$propertySchema['readOnly'] = true;
|
||||
}
|
||||
if (!$swagger && false === $propertyMetadata->readable) {
|
||||
$propertySchema['writeOnly'] = true;
|
||||
}
|
||||
if (null !== $description = $propertyMetadata->description) {
|
||||
$propertySchema['description'] = $description;
|
||||
}
|
||||
|
||||
$deprecationReason = $propertyMetadata->deprecationReason;
|
||||
|
||||
// see https://github.com/json-schema-org/json-schema-spec/pull/737
|
||||
if (!$swagger && null !== $deprecationReason) {
|
||||
$propertySchema['deprecated'] = true;
|
||||
}
|
||||
|
||||
if (!empty($default = $propertyMetadata->default)) {
|
||||
if ($default instanceof \BackedEnum) {
|
||||
$default = $default->value;
|
||||
}
|
||||
$propertySchema['default'] = $default;
|
||||
}
|
||||
|
||||
if (!empty($example = $propertyMetadata->example)) {
|
||||
$propertySchema['example'] = $example;
|
||||
}
|
||||
|
||||
if (!isset($propertySchema['example']) && isset($propertySchema['default'])) {
|
||||
$propertySchema['example'] = $propertySchema['default'];
|
||||
}
|
||||
|
||||
$propertySchema['type'] = $propertyMetadata->type;
|
||||
$propertySchema['nullable'] = $propertyMetadata->nullable;
|
||||
|
||||
$propertySchema = new \ArrayObject($propertySchema);
|
||||
|
||||
$schema->getDefinitions()[$definitionName]['properties'][$normalizedPropertyName] = $propertySchema;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
67
src/ApiPlatform/DocumentedAPIProperty.php
Normal file
67
src/ApiPlatform/DocumentedAPIProperty.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform;
|
||||
|
||||
/**
|
||||
* When this attribute is applied to a class, an property will be added to the API documentation using the given parameters.
|
||||
* This is useful for adding properties to the API documentation, that are not existing in the entity class itself,
|
||||
* but get added by a normalizer.
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_CLASS| \Attribute::IS_REPEATABLE)]
|
||||
final class DocumentedAPIProperty
|
||||
{
|
||||
public function __construct(
|
||||
/**
|
||||
* @param string $schemaName The name of the schema to add the property to (e.g. "Part-Read")
|
||||
*/
|
||||
public readonly string $schemaName,
|
||||
/**
|
||||
* @var string $property The name of the property to add to the schema
|
||||
*/
|
||||
public readonly string $property,
|
||||
public readonly string $type = 'string',
|
||||
public readonly bool $nullable = true,
|
||||
/**
|
||||
* @var string $description The description of the property
|
||||
*/
|
||||
public readonly ?string $description = null,
|
||||
/**
|
||||
* @var bool True if the property is readable, false otherwise
|
||||
*/
|
||||
public readonly bool $readable = true,
|
||||
/**
|
||||
* @var bool True if the property is writable, false otherwise
|
||||
*/
|
||||
public readonly bool $writeable = false,
|
||||
/**
|
||||
* @var string|null The deprecation reason of the property
|
||||
*/
|
||||
public readonly ?string $deprecationReason = null,
|
||||
/** @var mixed The default value of this property */
|
||||
public readonly mixed $default = null,
|
||||
public readonly mixed $example = null,
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
88
src/ApiPlatform/Filter/EntityFilter.php
Normal file
88
src/ApiPlatform/Filter/EntityFilter.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform\Filter;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
|
||||
|
||||
class EntityFilter extends AbstractFilter
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
ManagerRegistry $managerRegistry,
|
||||
private readonly EntityFilterHelper $filter_helper,
|
||||
LoggerInterface $logger = null,
|
||||
?array $properties = null,
|
||||
?NameConverterInterface $nameConverter = null
|
||||
) {
|
||||
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
|
||||
}
|
||||
|
||||
protected function filterProperty(
|
||||
string $property,
|
||||
$value,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
Operation $operation = null,
|
||||
array $context = []
|
||||
): void {
|
||||
if (
|
||||
!$this->isPropertyEnabled($property, $resourceClass) ||
|
||||
!$this->isPropertyMapped($property, $resourceClass, true)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$metadata = $this->getClassMetadata($resourceClass);
|
||||
$target_class = $metadata->getAssociationTargetClass($property);
|
||||
//If it is not an association we can not filter the property
|
||||
if (!$target_class) {
|
||||
return;
|
||||
}
|
||||
|
||||
$elements = $this->filter_helper->valueToEntityArray($value, $target_class);
|
||||
|
||||
$parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
|
||||
$queryBuilder
|
||||
->andWhere(sprintf('o.%s IN (:%s)', $property, $parameterName))
|
||||
->setParameter($parameterName, $elements);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getDescription(string $resourceClass): array
|
||||
{
|
||||
return $this->filter_helper->getDescription($this->properties);
|
||||
}
|
||||
}
|
||||
104
src/ApiPlatform/Filter/EntityFilterHelper.php
Normal file
104
src/ApiPlatform/Filter/EntityFilterHelper.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform\Filter;
|
||||
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
class EntityFilterHelper
|
||||
{
|
||||
public function __construct(private NodesListBuilder $nodesListBuilder,
|
||||
private EntityManagerInterface $entityManager)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function valueToEntityArray(string $value, string $target_class): array
|
||||
{
|
||||
//Convert value to IDs:
|
||||
$elements = [];
|
||||
|
||||
//Split the given value by comm
|
||||
foreach (explode(',', $value) as $id) {
|
||||
if (trim($id) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Check if the given value ends with a plus, then we want to include all direct children
|
||||
$include_children = false;
|
||||
$include_recursive = false;
|
||||
if (str_ends_with($id, '++')) { //Plus Plus means include all children recursively
|
||||
$id = substr($id, 0, -2);
|
||||
$include_recursive = true;
|
||||
} elseif (str_ends_with($id, '+')) {
|
||||
$id = substr($id, 0, -1);
|
||||
$include_children = true;
|
||||
}
|
||||
|
||||
//Get a (shallow) reference to the entitity
|
||||
$element = $this->entityManager->getReference($target_class, (int) $id);
|
||||
$elements[] = $element;
|
||||
|
||||
//If $element is not structural we are done
|
||||
if (!is_a($element, AbstractStructuralDBElement::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//Get the recursive list of children
|
||||
if ($include_recursive) {
|
||||
$elements = array_merge($elements, $this->nodesListBuilder->getChildrenFlatList($element));
|
||||
} elseif ($include_children) {
|
||||
$elements = array_merge($elements, $element->getChildren()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
public function getDescription(array $properties): array
|
||||
{
|
||||
if (!$properties) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$description = [];
|
||||
foreach ($properties as $property => $strategy) {
|
||||
$description["$property"] = [
|
||||
'property' => $property,
|
||||
'type' => Type::BUILTIN_TYPE_STRING,
|
||||
'required' => false,
|
||||
'description' => 'Filter using a comma seperated list of element IDs. Use + to include all direct children and ++ to include all children recursively.',
|
||||
'openapi' => [
|
||||
'example' => '',
|
||||
'allowReserved' => false,// if true, query parameters will be not percent-encoded
|
||||
'allowEmptyValue' => true,
|
||||
'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green
|
||||
],
|
||||
];
|
||||
}
|
||||
return $description;
|
||||
}
|
||||
}
|
||||
80
src/ApiPlatform/Filter/LikeFilter.php
Normal file
80
src/ApiPlatform/Filter/LikeFilter.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform\Filter;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
final class LikeFilter extends AbstractFilter
|
||||
{
|
||||
|
||||
protected function filterProperty(
|
||||
string $property,
|
||||
$value,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
Operation $operation = null,
|
||||
array $context = []
|
||||
): void {
|
||||
// Otherwise filter is applied to order and page as well
|
||||
if (
|
||||
!$this->isPropertyEnabled($property, $resourceClass) ||
|
||||
!$this->isPropertyMapped($property, $resourceClass)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
|
||||
$queryBuilder
|
||||
->andWhere(sprintf('o.%s LIKE :%s', $property, $parameterName))
|
||||
->setParameter($parameterName, $value);
|
||||
}
|
||||
|
||||
public function getDescription(string $resourceClass): array
|
||||
{
|
||||
if (!$this->properties) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$description = [];
|
||||
foreach ($this->properties as $property => $strategy) {
|
||||
$description["$property"] = [
|
||||
'property' => $property,
|
||||
'type' => Type::BUILTIN_TYPE_STRING,
|
||||
'required' => false,
|
||||
'description' => 'Filter using a LIKE SQL expression. Use % as wildcard for multiple characters and _ for single characters. For example, to search for all items containing foo, use foo. To search for all items starting with foo, use foo%. To search for all items ending with foo, use %foo',
|
||||
'openapi' => [
|
||||
'example' => '',
|
||||
'allowReserved' => false,// if true, query parameters will be not percent-encoded
|
||||
'allowEmptyValue' => true,
|
||||
'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green
|
||||
],
|
||||
];
|
||||
}
|
||||
return $description;
|
||||
}
|
||||
}
|
||||
83
src/ApiPlatform/Filter/PartStoragelocationFilter.php
Normal file
83
src/ApiPlatform/Filter/PartStoragelocationFilter.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform\Filter;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use App\Entity\Base\AbstractStructuralDBElement;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
|
||||
|
||||
class PartStoragelocationFilter extends AbstractFilter
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
ManagerRegistry $managerRegistry,
|
||||
private readonly EntityFilterHelper $filter_helper,
|
||||
LoggerInterface $logger = null,
|
||||
?array $properties = null,
|
||||
?NameConverterInterface $nameConverter = null
|
||||
) {
|
||||
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
|
||||
}
|
||||
|
||||
protected function filterProperty(
|
||||
string $property,
|
||||
$value,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
Operation $operation = null,
|
||||
array $context = []
|
||||
): void {
|
||||
//Do not check for mapping here, as we are using a virtual property
|
||||
if (
|
||||
!$this->isPropertyEnabled($property, $resourceClass)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$elements = $this->filter_helper->valueToEntityArray($value, StorageLocation::class);
|
||||
|
||||
$parameterName = $queryNameGenerator->generateParameterName($property); // Generate a unique parameter name to avoid collisions with other filters
|
||||
$queryBuilder
|
||||
->leftJoin('o.partLots', 'partLots')
|
||||
->andWhere(sprintf('partLots.storage_location IN (:%s)', $parameterName))
|
||||
->setParameter($parameterName, $elements);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getDescription(string $resourceClass): array
|
||||
{
|
||||
return $this->filter_helper->getDescription($this->properties);
|
||||
}
|
||||
}
|
||||
72
src/ApiPlatform/FixInheritanceMappingMetadataFacory.php
Normal file
72
src/ApiPlatform/FixInheritanceMappingMetadataFacory.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform;
|
||||
|
||||
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
|
||||
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
|
||||
/**
|
||||
* API Platform has problems with single table inheritance, as it assumes that they all have different endpoints.
|
||||
* This decorator fixes this problem by using the parent class for the metadata collection.
|
||||
*/
|
||||
#[AsDecorator('api_platform.metadata.resource.metadata_collection_factory')]
|
||||
class FixInheritanceMappingMetadataFacory implements ResourceMetadataCollectionFactoryInterface
|
||||
{
|
||||
private const SINGLE_INHERITANCE_ENTITY_CLASSES = [
|
||||
Attachment::class,
|
||||
AbstractParameter::class,
|
||||
];
|
||||
|
||||
private array $cache = [];
|
||||
|
||||
public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $decorated)
|
||||
{
|
||||
}
|
||||
|
||||
public function create(string $resourceClass): ResourceMetadataCollection
|
||||
{
|
||||
//If we already have a cached value, we can return it
|
||||
if (isset($this->cache[$resourceClass])) {
|
||||
return $this->decorated->create($this->cache[$resourceClass]);
|
||||
}
|
||||
|
||||
//Check if the resourceClass is a single inheritance class, then we can use the parent class to access it
|
||||
foreach (self::SINGLE_INHERITANCE_ENTITY_CLASSES as $class) {
|
||||
if (is_a($resourceClass, $class, true)) {
|
||||
$this->cache[$resourceClass] = $class;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//If it was not found in the list of single inheritance classes, we can use the original class
|
||||
if (!isset($this->cache[$resourceClass])) {
|
||||
$this->cache[$resourceClass] = $resourceClass;
|
||||
}
|
||||
|
||||
return $this->decorated->create($this->cache[$resourceClass] ?? $resourceClass);
|
||||
}
|
||||
}
|
||||
50
src/ApiPlatform/OpenApiFactoryDecorator.php
Normal file
50
src/ApiPlatform/OpenApiFactoryDecorator.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiPlatform;
|
||||
|
||||
use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
|
||||
use ApiPlatform\OpenApi\Model\SecurityScheme;
|
||||
use ApiPlatform\OpenApi\OpenApi;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
|
||||
|
||||
#[AsDecorator('api_platform.openapi.factory')]
|
||||
class OpenApiFactoryDecorator implements OpenApiFactoryInterface
|
||||
{
|
||||
public function __construct(private readonly OpenApiFactoryInterface $decorated)
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(array $context = []): OpenApi
|
||||
{
|
||||
$openApi = $this->decorated->__invoke($context);
|
||||
$securitySchemes = $openApi->getComponents()->getSecuritySchemes() ?: new \ArrayObject();
|
||||
$securitySchemes['access_token'] = new SecurityScheme(
|
||||
type: 'http',
|
||||
description: 'Use an API token to authenticate',
|
||||
name: 'Authorization',
|
||||
scheme: 'bearer',
|
||||
);
|
||||
return $openApi;
|
||||
}
|
||||
}
|
||||
67
src/ApiResource/PartDBInfo.php
Normal file
67
src/ApiResource/PartDBInfo.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\ApiResource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\Get;
|
||||
use ApiPlatform\OpenApi\Model\Operation;
|
||||
use ApiPlatform\Serializer\Filter\PropertyFilter;
|
||||
use App\State\PartDBInfoProvider;
|
||||
|
||||
/**
|
||||
* This class is used to provide various information about the system.
|
||||
*/
|
||||
#[ApiResource(
|
||||
uriTemplate: '/info.{_format}',
|
||||
description: 'Basic information about Part-DB like version, title, etc.',
|
||||
operations: [new Get(openapi: new Operation(summary: 'Get basic information about the installed Part-DB instance.'))],
|
||||
provider: PartDBInfoProvider::class
|
||||
)]
|
||||
#[ApiFilter(PropertyFilter::class)]
|
||||
class PartDBInfo
|
||||
{
|
||||
public function __construct(
|
||||
/** The installed Part-DB version */
|
||||
public readonly string $version,
|
||||
/** The Git branch name of the Part-DB version (or null, if not installed via git) */
|
||||
public readonly string|null $git_branch,
|
||||
/** The Git branch commit of the Part-DB version (or null, if not installed via git) */
|
||||
public readonly string|null $git_commit,
|
||||
/** The name of this Part-DB instance */
|
||||
public readonly string $title,
|
||||
/** The banner, shown on homepage (markdown encoded) */
|
||||
public readonly string $banner,
|
||||
/** The configured default URI for Part-DB */
|
||||
public readonly string $default_uri,
|
||||
/** The global timezone of this Part-DB */
|
||||
public readonly string $global_timezone,
|
||||
/** The base currency of Part-DB, used as internal representation of monetary values */
|
||||
public readonly string $base_currency,
|
||||
/** The configured default language of Part-DB */
|
||||
public readonly string $global_locale,
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -65,12 +65,12 @@ class ShowEventLogCommand extends Command
|
||||
$max_page = (int) ceil($total_count / $limit);
|
||||
|
||||
if ($page > $max_page && $max_page > 0) {
|
||||
$io->error("There is no page ${page}! The maximum page is ${max_page}.");
|
||||
$io->error("There is no page $page! The maximum page is $max_page.");
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->note("There are a total of ${total_count} log entries in the DB.");
|
||||
$io->note("There are a total of $total_count log entries in the DB.");
|
||||
|
||||
$continue = true;
|
||||
while ($continue && $page <= $max_page) {
|
||||
@@ -105,7 +105,7 @@ class ShowEventLogCommand extends Command
|
||||
$entries = $this->repo->getLogsOrderedByTimestamp($sorting, $limit, $offset);
|
||||
|
||||
$table = new Table($output);
|
||||
$table->setHeaderTitle("Page ${page} / ${max_page}");
|
||||
$table->setHeaderTitle("Page $page / $max_page");
|
||||
$headers = ['ID', 'Timestamp', 'Type', 'User', 'Target Type', 'Target'];
|
||||
if ($showExtra) {
|
||||
$headers[] = 'Extra data';
|
||||
|
||||
@@ -30,7 +30,7 @@ use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
use App\Entity\UserSystem\Group;
|
||||
@@ -85,7 +85,7 @@ class ConvertBBCodeCommand extends Command
|
||||
return [
|
||||
Part::class => ['description', 'comment'],
|
||||
AttachmentType::class => ['comment'],
|
||||
Storelocation::class => ['comment'],
|
||||
StorageLocation::class => ['comment'],
|
||||
Project::class => ['comment'],
|
||||
Category::class => ['comment'],
|
||||
Manufacturer::class => ['comment'],
|
||||
|
||||
@@ -55,6 +55,7 @@ final class PermissionsConfiguration implements ConfigurationInterface
|
||||
->scalarNode('name')->end()
|
||||
->scalarNode('label')->end()
|
||||
->scalarNode('bit')->end()
|
||||
->scalarNode('apiTokenRole')->defaultNull()->end()
|
||||
->arrayNode('alsoSet')
|
||||
->beforeNormalization()->castToArray()->end()->scalarPrototype()->end();
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\AdminPages;
|
||||
|
||||
use App\Entity\Attachments\StorelocationAttachment;
|
||||
use App\Entity\Parameters\StorelocationParameter;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Attachments\StorageLocationAttachment;
|
||||
use App\Entity\Parameters\StorageLocationParameter;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Form\AdminPages\StorelocationAdminForm;
|
||||
use App\Services\ImportExportSystem\EntityExporter;
|
||||
use App\Services\ImportExportSystem\EntityImporter;
|
||||
@@ -39,24 +39,24 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
* @see \App\Tests\Controller\AdminPages\StorelocationControllerTest
|
||||
*/
|
||||
#[Route(path: '/store_location')]
|
||||
class StorelocationController extends BaseAdminController
|
||||
class StorageLocationController extends BaseAdminController
|
||||
{
|
||||
protected string $entity_class = Storelocation::class;
|
||||
protected string $entity_class = StorageLocation::class;
|
||||
protected string $twig_template = 'admin/storelocation_admin.html.twig';
|
||||
protected string $form_class = StorelocationAdminForm::class;
|
||||
protected string $route_base = 'store_location';
|
||||
protected string $attachment_class = StorelocationAttachment::class;
|
||||
protected ?string $parameter_class = StorelocationParameter::class;
|
||||
protected string $attachment_class = StorageLocationAttachment::class;
|
||||
protected ?string $parameter_class = StorageLocationParameter::class;
|
||||
|
||||
#[Route(path: '/{id}', name: 'store_location_delete', methods: ['DELETE'])]
|
||||
public function delete(Request $request, Storelocation $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
public function delete(Request $request, StorageLocation $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'store_location_edit')]
|
||||
#[Route(path: '/{id}', requirements: ['id' => '\d+'])]
|
||||
public function edit(Storelocation $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
public function edit(StorageLocation $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
|
||||
{
|
||||
return $this->_edit($entity, $request, $em, $timestamp);
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class StorelocationController extends BaseAdminController
|
||||
#[Route(path: '/new', name: 'store_location_new')]
|
||||
#[Route(path: '/{id}/clone', name: 'store_location_clone')]
|
||||
#[Route(path: '/')]
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Storelocation $entity = null): Response
|
||||
public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?StorageLocation $entity = null): Response
|
||||
{
|
||||
return $this->_new($request, $em, $importer, $entity);
|
||||
}
|
||||
@@ -76,7 +76,7 @@ class StorelocationController extends BaseAdminController
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/export', name: 'store_location_export')]
|
||||
public function exportEntity(Storelocation $entity, EntityExporter $exporter, Request $request): Response
|
||||
public function exportEntity(StorageLocation $entity, EntityExporter $exporter, Request $request): Response
|
||||
{
|
||||
return $this->_exportEntity($entity, $exporter, $request);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ namespace App\Controller;
|
||||
use App\DataTables\LogDataTable;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\Misc\GitVersionInfo;
|
||||
use App\Services\System\BannerHelper;
|
||||
use App\Services\System\UpdateAvailableManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
@@ -38,29 +39,11 @@ use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
class HomepageController extends AbstractController
|
||||
{
|
||||
public function __construct(protected CacheInterface $cache, protected KernelInterface $kernel, protected DataTableFactory $dataTable)
|
||||
public function __construct(private readonly DataTableFactory $dataTable, private readonly BannerHelper $bannerHelper)
|
||||
{
|
||||
}
|
||||
|
||||
public function getBanner(): string
|
||||
{
|
||||
$banner = $this->getParameter('partdb.banner');
|
||||
if (!is_string($banner)) {
|
||||
throw new \RuntimeException('The parameter "partdb.banner" must be a string.');
|
||||
}
|
||||
if (empty($banner)) {
|
||||
$banner_path = $this->kernel->getProjectDir()
|
||||
.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md';
|
||||
|
||||
$tmp = file_get_contents($banner_path);
|
||||
if (false === $tmp) {
|
||||
throw new \RuntimeException('The banner file could not be read.');
|
||||
}
|
||||
$banner = $tmp;
|
||||
}
|
||||
|
||||
return $banner;
|
||||
}
|
||||
|
||||
#[Route(path: '/', name: 'homepage')]
|
||||
public function homepage(Request $request, GitVersionInfo $versionInfo, EntityManagerInterface $entityManager,
|
||||
@@ -96,7 +79,7 @@ class HomepageController extends AbstractController
|
||||
}
|
||||
|
||||
return $this->render('homepage.html.twig', [
|
||||
'banner' => $this->getBanner(),
|
||||
'banner' => $this->bannerHelper->getBanner(),
|
||||
'git_branch' => $versionInfo->getGitBranchName(),
|
||||
'git_commit' => $versionInfo->getGitCommitHash(),
|
||||
'show_first_steps' => $show_first_steps,
|
||||
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Exceptions\AttachmentDownloadException;
|
||||
use App\Form\InfoProviderSystem\PartSearchType;
|
||||
use App\Form\Part\PartBaseType;
|
||||
@@ -32,6 +33,7 @@ use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||
use App\Services\LogSystem\EventCommentHelper;
|
||||
use App\Services\Parts\PartFormHelper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -61,7 +63,8 @@ class InfoProviderController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route('/search', name: 'info_providers_search')]
|
||||
public function search(Request $request): Response
|
||||
#[Route('/update/{target}', name: 'info_providers_update_part_search')]
|
||||
public function search(Request $request, #[MapEntity(id: 'target')] ?Part $update_target): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
@@ -70,6 +73,12 @@ class InfoProviderController extends AbstractController
|
||||
|
||||
$results = null;
|
||||
|
||||
//When we are updating a part, use its name as keyword, to make searching easier
|
||||
//However we can only do this, if the form was not submitted yet
|
||||
if ($update_target !== null && !$form->isSubmitted()) {
|
||||
$form->get('keyword')->setData($update_target->getName());
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$keyword = $form->get('keyword')->getData();
|
||||
$providers = $form->get('providers')->getData();
|
||||
@@ -80,6 +89,7 @@ class InfoProviderController extends AbstractController
|
||||
return $this->render('info_providers/search/part_search.html.twig', [
|
||||
'form' => $form,
|
||||
'results' => $results,
|
||||
'update_target' => $update_target
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
@@ -36,6 +36,7 @@ use App\Exceptions\AttachmentDownloadException;
|
||||
use App\Form\Part\PartBaseType;
|
||||
use App\Services\Attachments\AttachmentSubmitHandler;
|
||||
use App\Services\Attachments\PartPreviewGenerator;
|
||||
use App\Services\EntityMergers\Mergers\PartMerger;
|
||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||
use App\Services\LogSystem\EventCommentHelper;
|
||||
use App\Services\LogSystem\HistoryHelper;
|
||||
@@ -201,8 +202,8 @@ class PartController extends AbstractController
|
||||
}
|
||||
|
||||
$store_id = $request->get('storelocation', null);
|
||||
$storelocation = $store_id ? $em->find(Storelocation::class, $store_id) : null;
|
||||
if ($storelocation instanceof Storelocation && $new_part->getPartLots()->isEmpty()) {
|
||||
$storelocation = $store_id ? $em->find(StorageLocation::class, $store_id) : null;
|
||||
if ($storelocation instanceof StorageLocation && $new_part->getPartLots()->isEmpty()) {
|
||||
$partLot = new PartLot();
|
||||
$partLot->setStorageLocation($storelocation);
|
||||
$partLot->setInstockUnknown(true);
|
||||
@@ -233,6 +234,48 @@ class PartController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{target}/merge/{other}', name: 'part_merge')]
|
||||
public function merge(Request $request, Part $target, Part $other, PartMerger $partMerger): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('edit', $target);
|
||||
$this->denyAccessUnlessGranted('delete', $other);
|
||||
|
||||
//Save the old name of the target part for the template
|
||||
$target_name = $target->getName();
|
||||
|
||||
$this->addFlash('notice', t('part.merge.flash.please_review'));
|
||||
|
||||
$merged = $partMerger->merge($target, $other);
|
||||
return $this->renderPartForm('merge', $request, $merged, [], [
|
||||
'tname_before' => $target_name,
|
||||
'other_part' => $other,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}/from_info_provider/{providerKey}/{providerId}/update', name: 'info_providers_update_part', requirements: ['providerId' => '.+'])]
|
||||
public function updateFromInfoProvider(Part $part, Request $request, string $providerKey, string $providerId,
|
||||
PartInfoRetriever $infoRetriever, PartMerger $partMerger): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('edit', $part);
|
||||
$this->denyAccessUnlessGranted('@info_providers.create_parts');
|
||||
|
||||
//Save the old name of the target part for the template
|
||||
$old_name = $part->getName();
|
||||
|
||||
$dto = $infoRetriever->getDetails($providerKey, $providerId);
|
||||
$provider_part = $infoRetriever->dtoToPart($dto);
|
||||
|
||||
$part = $partMerger->merge($part, $provider_part);
|
||||
|
||||
$this->addFlash('notice', t('part.merge.flash.please_review'));
|
||||
|
||||
return $this->renderPartForm('update_from_ip', $request, $part, [
|
||||
'info_provider_dto' => $dto,
|
||||
], [
|
||||
'tname_before' => $old_name
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function provides a common implementation for methods, which use the part form.
|
||||
* @param Request $request
|
||||
@@ -240,10 +283,10 @@ class PartController extends AbstractController
|
||||
* @param array $form_options
|
||||
* @return Response
|
||||
*/
|
||||
private function renderPartForm(string $mode, Request $request, Part $data, array $form_options = []): Response
|
||||
private function renderPartForm(string $mode, Request $request, Part $data, array $form_options = [], array $merge_infos = []): Response
|
||||
{
|
||||
//Ensure that mode is either 'new' or 'edit
|
||||
if (!in_array($mode, ['new', 'edit'], true)) {
|
||||
if (!in_array($mode, ['new', 'edit', 'merge', 'update_from_ip'], true)) {
|
||||
throw new \InvalidArgumentException('Invalid mode given');
|
||||
}
|
||||
|
||||
@@ -276,6 +319,12 @@ class PartController extends AbstractController
|
||||
$this->commentHelper->setMessage($form['log_comment']->getData());
|
||||
|
||||
$this->em->persist($new_part);
|
||||
|
||||
//When we are in merge mode, we have to remove the other part
|
||||
if ($mode === 'merge') {
|
||||
$this->em->remove($merge_infos['other_part']);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
if ($mode === 'new') {
|
||||
$this->addFlash('success', 'part.created_flash');
|
||||
@@ -310,12 +359,18 @@ class PartController extends AbstractController
|
||||
$template = 'parts/edit/new_part.html.twig';
|
||||
} else if ($mode === 'edit') {
|
||||
$template = 'parts/edit/edit_part_info.html.twig';
|
||||
} else if ($mode === 'merge') {
|
||||
$template = 'parts/edit/merge_parts.html.twig';
|
||||
} else if ($mode === 'update_from_ip') {
|
||||
$template = 'parts/edit/update_from_ip.html.twig';
|
||||
}
|
||||
|
||||
return $this->render($template,
|
||||
[
|
||||
'part' => $new_part,
|
||||
'form' => $form,
|
||||
'merge_old_name' => $merge_infos['tname_before'] ?? null,
|
||||
'merge_other' => $merge_infos['other_part'] ?? null
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -345,23 +400,35 @@ class PartController extends AbstractController
|
||||
$amount = (float) $request->request->get('amount');
|
||||
$comment = $request->request->get('comment');
|
||||
$action = $request->request->get('action');
|
||||
$delete_lot_if_empty = $request->request->getBoolean('delete_lot_if_empty', false);
|
||||
|
||||
$timestamp = null;
|
||||
$timestamp_str = $request->request->getString('timestamp', '');
|
||||
//Try to parse the timestamp
|
||||
if($timestamp_str !== '') {
|
||||
$timestamp = new DateTime($timestamp_str);
|
||||
}
|
||||
|
||||
//Ensure that the timestamp is not in the future
|
||||
if($timestamp !== null && $timestamp > new DateTime("+20min")) {
|
||||
throw new \LogicException("The timestamp must not be in the future!");
|
||||
}
|
||||
|
||||
try {
|
||||
switch ($action) {
|
||||
case "withdraw":
|
||||
case "remove":
|
||||
$this->denyAccessUnlessGranted('withdraw', $partLot);
|
||||
$withdrawAddHelper->withdraw($partLot, $amount, $comment);
|
||||
$withdrawAddHelper->withdraw($partLot, $amount, $comment, $timestamp, $delete_lot_if_empty);
|
||||
break;
|
||||
case "add":
|
||||
$this->denyAccessUnlessGranted('add', $partLot);
|
||||
$withdrawAddHelper->add($partLot, $amount, $comment);
|
||||
$withdrawAddHelper->add($partLot, $amount, $comment, $timestamp);
|
||||
break;
|
||||
case "move":
|
||||
$this->denyAccessUnlessGranted('move', $partLot);
|
||||
$this->denyAccessUnlessGranted('move', $targetLot);
|
||||
$withdrawAddHelper->move($partLot, $targetLot, $amount, $comment);
|
||||
$withdrawAddHelper->move($partLot, $targetLot, $amount, $comment, $timestamp, $delete_lot_if_empty);
|
||||
break;
|
||||
default:
|
||||
throw new \RuntimeException("Unknown action!");
|
||||
|
||||
@@ -29,7 +29,7 @@ use App\DataTables\PartsDataTable;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Exceptions\InvalidRegexException;
|
||||
use App\Form\Filters\PartFilterType;
|
||||
@@ -214,7 +214,7 @@ class PartListsController extends AbstractController
|
||||
}
|
||||
|
||||
#[Route(path: '/store_location/{id}/parts', name: 'part_list_store_location')]
|
||||
public function showStorelocation(Storelocation $storelocation, Request $request): Response
|
||||
public function showStorelocation(StorageLocation $storelocation, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@storelocations.read');
|
||||
|
||||
@@ -226,7 +226,7 @@ class PartListsController extends AbstractController
|
||||
$this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value'));
|
||||
}, [
|
||||
'entity' => $storelocation,
|
||||
'repo' => $this->entityManager->getRepository(Storelocation::class),
|
||||
'repo' => $this->entityManager->getRepository(StorageLocation::class),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,8 +42,10 @@ declare(strict_types=1);
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Form\LabelSystem\ScanDialogType;
|
||||
use App\Services\LabelSystem\Barcodes\BarcodeNormalizer;
|
||||
use App\Services\LabelSystem\Barcodes\BarcodeScanHelper;
|
||||
use App\Services\LabelSystem\Barcodes\BarcodeRedirector;
|
||||
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
|
||||
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
@@ -55,7 +57,7 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
#[Route(path: '/scan')]
|
||||
class ScanController extends AbstractController
|
||||
{
|
||||
public function __construct(protected BarcodeRedirector $barcodeParser, protected BarcodeNormalizer $barcodeNormalizer)
|
||||
public function __construct(protected BarcodeRedirector $barcodeParser, protected BarcodeScanHelper $barcodeNormalizer)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -69,14 +71,14 @@ class ScanController extends AbstractController
|
||||
|
||||
if ($input === null && $form->isSubmitted() && $form->isValid()) {
|
||||
$input = $form['input']->getData();
|
||||
$mode = $form['mode']->getData();
|
||||
}
|
||||
|
||||
if ($input !== null) {
|
||||
try {
|
||||
[$type, $id] = $this->barcodeNormalizer->normalizeBarcodeContent($input);
|
||||
|
||||
$scan_result = $this->barcodeNormalizer->scanBarcodeContent($input, $mode ?? null);
|
||||
try {
|
||||
return $this->redirect($this->barcodeParser->getRedirectURL($type, $id));
|
||||
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
|
||||
} catch (EntityNotFoundException) {
|
||||
$this->addFlash('success', 'scan.qr_not_found');
|
||||
}
|
||||
@@ -95,10 +97,23 @@ class ScanController extends AbstractController
|
||||
*/
|
||||
public function scanQRCode(string $type, int $id): Response
|
||||
{
|
||||
$type = strtolower($type);
|
||||
|
||||
try {
|
||||
$this->addFlash('success', 'scan.qr_success');
|
||||
|
||||
return $this->redirect($this->barcodeParser->getRedirectURL($type, $id));
|
||||
if (!isset(BarcodeScanHelper::QR_TYPE_MAP[$type])) {
|
||||
throw new InvalidArgumentException('Unknown type: '.$type);
|
||||
}
|
||||
//Construct the scan result manually, as we don't have a barcode here
|
||||
$scan_result = new BarcodeScanResult(
|
||||
target_type: BarcodeScanHelper::QR_TYPE_MAP[$type],
|
||||
target_id: $id,
|
||||
//The routes are only used on the internal generated QR codes
|
||||
source_type: BarcodeSourceType::INTERNAL
|
||||
);
|
||||
|
||||
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
|
||||
} catch (EntityNotFoundException) {
|
||||
$this->addFlash('success', 'scan.qr_not_found');
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Services\Trees\ToolsTreeBuilder;
|
||||
use App\Services\Trees\TreeViewGenerator;
|
||||
@@ -80,10 +80,10 @@ class TreeController extends AbstractController
|
||||
|
||||
#[Route(path: '/location/{id}', name: 'tree_location')]
|
||||
#[Route(path: '/locations', name: 'tree_location_root')]
|
||||
public function locationTree(?Storelocation $location = null): JsonResponse
|
||||
public function locationTree(?StorageLocation $location = null): JsonResponse
|
||||
{
|
||||
if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) {
|
||||
$tree = $this->treeGenerator->getTreeView(Storelocation::class, $location, 'list_parts_root');
|
||||
$tree = $this->treeGenerator->getTreeView(StorageLocation::class, $location, 'list_parts_root');
|
||||
} else {
|
||||
return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ use App\Entity\Parameters\GroupParameter;
|
||||
use App\Entity\Parameters\ManufacturerParameter;
|
||||
use App\Entity\Parameters\MeasurementUnitParameter;
|
||||
use App\Entity\Parameters\PartParameter;
|
||||
use App\Entity\Parameters\StorelocationParameter;
|
||||
use App\Entity\Parameters\StorageLocationParameter;
|
||||
use App\Entity\Parameters\SupplierParameter;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\PriceInformations\Currency;
|
||||
@@ -102,7 +102,7 @@ class TypeaheadController extends AbstractController
|
||||
'device' => ProjectParameter::class,
|
||||
'footprint' => FootprintParameter::class,
|
||||
'manufacturer' => ManufacturerParameter::class,
|
||||
'storelocation' => StorelocationParameter::class,
|
||||
'storelocation' => StorageLocationParameter::class,
|
||||
'supplier' => SupplierParameter::class,
|
||||
'attachment_type' => AttachmentTypeParameter::class,
|
||||
'group' => GroupParameter::class,
|
||||
|
||||
@@ -169,8 +169,14 @@ class UserController extends BaseAdminController
|
||||
#[Route(path: '/{id}', name: 'user_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
|
||||
public function delete(Request $request, User $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse
|
||||
{
|
||||
//Disallow deleting the anonymous user
|
||||
if (User::ID_ANONYMOUS === $entity->getID()) {
|
||||
throw new InvalidArgumentException('You can not delete the anonymous user! It is needed for permission checking without a logged in user');
|
||||
throw new \LogicException('You can not delete the anonymous user! It is needed for permission checking without a logged in user');
|
||||
}
|
||||
|
||||
//Disallow deleting the current logged-in user
|
||||
if ($entity === $this->getUser()) {
|
||||
throw new \LogicException('You can not delete your own user account!');
|
||||
}
|
||||
|
||||
return $this->_delete($request, $entity, $recursionHelper);
|
||||
|
||||
@@ -23,6 +23,8 @@ declare(strict_types=1);
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\UserSystem\ApiToken;
|
||||
use App\Entity\UserSystem\ApiTokenLevel;
|
||||
use App\Entity\UserSystem\U2FKey;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Entity\UserSystem\WebauthnKey;
|
||||
@@ -39,6 +41,8 @@ use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Google\GoogleAuthenticator
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
@@ -395,4 +399,99 @@ class UserSettingsController extends AbstractController
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
#[Route('/api_token/create', name: 'user_api_token_create')]
|
||||
public function addApiToken(Request $request, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@api.manage_tokens');
|
||||
//When user change its settings, he should be logged in fully.
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
$token = new ApiToken();
|
||||
if (!$this->getUser() instanceof User) {
|
||||
throw new RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
$token->setUser($this->getUser());
|
||||
|
||||
$secret = null;
|
||||
|
||||
$form = $this->createFormBuilder($token)
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'api_tokens.name',
|
||||
])
|
||||
->add('level', EnumType::class, [
|
||||
'class' => ApiTokenLevel::class,
|
||||
'label' => 'api_tokens.access_level',
|
||||
'help' => 'api_tokens.access_level.help',
|
||||
'choice_label' => fn (ApiTokenLevel $level) => $level->getTranslationKey(),
|
||||
])
|
||||
->add('valid_until', DateTimeType::class, [
|
||||
'label' => 'api_tokens.expiration_date',
|
||||
'widget' => 'single_text',
|
||||
'help' => 'api_tokens.expiration_date.help',
|
||||
'required' => false,
|
||||
'html5' => true
|
||||
])
|
||||
->add('submit', SubmitType::class, [
|
||||
'label' => 'save',
|
||||
])
|
||||
->getForm();
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$entityManager->persist($token);
|
||||
$entityManager->flush();
|
||||
|
||||
$secret = $token->getToken();
|
||||
}
|
||||
|
||||
return $this->render('users/api_token_create.html.twig', [
|
||||
'token' => $token,
|
||||
'form' => $form,
|
||||
'secret' => $secret,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/api_token/delete', name: 'user_api_tokens_delete', methods: ['DELETE'])]
|
||||
public function apiTokenRemove(Request $request, EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('@api.manage_tokens');
|
||||
//When user change its settings, he should be logged in fully.
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
|
||||
|
||||
$user = $this->getUser();
|
||||
if (!$user instanceof User) {
|
||||
throw new RuntimeException('This controller only works only for Part-DB User objects!');
|
||||
}
|
||||
|
||||
if (!$this->isCsrfTokenValid('delete'.$user->getID(), $request->request->get('_token'))) {
|
||||
$this->addFlash('error', 'csfr_invalid');
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
|
||||
//Extract the token id from the request
|
||||
$token_id = $request->request->getInt('token_id');
|
||||
|
||||
$token = $entityManager->find(ApiToken::class, $token_id);
|
||||
if ($token === null) {
|
||||
$this->addFlash('error', 'tfa_u2f.u2f_delete.not_existing');
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
//User can only delete its own API tokens
|
||||
if ($token->getUser() !== $user) {
|
||||
$this->addFlash('error', 'tfa_u2f.u2f_delete.access_denied');
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
|
||||
//Do the actual deletion
|
||||
$entityManager->remove($token);
|
||||
$entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'api_tokens.deleted');
|
||||
return $this->redirectToRoute('user_settings');
|
||||
}
|
||||
}
|
||||
|
||||
97
src/DataFixtures/APITokenFixtures.php
Normal file
97
src/DataFixtures/APITokenFixtures.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\DataFixtures;
|
||||
|
||||
use App\Entity\UserSystem\ApiToken;
|
||||
use App\Entity\UserSystem\ApiTokenLevel;
|
||||
use App\Entity\UserSystem\User;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
|
||||
use Doctrine\Persistence\ObjectManager;
|
||||
|
||||
class APITokenFixtures extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
public const TOKEN_READONLY = 'tcp_readonly';
|
||||
public const TOKEN_EDIT = 'tcp_edit';
|
||||
public const TOKEN_ADMIN = 'tcp_admin';
|
||||
public const TOKEN_FULL = 'tcp_full';
|
||||
public const TOKEN_EXPIRED = 'tcp_expired';
|
||||
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
/** @var User $admin_user */
|
||||
$admin_user = $this->getReference(UserFixtures::ADMIN);
|
||||
|
||||
$read_only_token = new ApiToken();
|
||||
$read_only_token->setUser($admin_user);
|
||||
$read_only_token->setLevel(ApiTokenLevel::READ_ONLY);
|
||||
$read_only_token->setName('read-only');
|
||||
$this->setTokenSecret($read_only_token, self::TOKEN_READONLY);
|
||||
$manager->persist($read_only_token);
|
||||
|
||||
$editor_token = new ApiToken();
|
||||
$editor_token->setUser($admin_user);
|
||||
$editor_token->setLevel(ApiTokenLevel::EDIT);
|
||||
$editor_token->setName('edit');
|
||||
$this->setTokenSecret($editor_token, self::TOKEN_EDIT);
|
||||
$manager->persist($editor_token);
|
||||
|
||||
$admin_token = new ApiToken();
|
||||
$admin_token->setUser($admin_user);
|
||||
$admin_token->setLevel(ApiTokenLevel::ADMIN);
|
||||
$admin_token->setName('admin');
|
||||
$this->setTokenSecret($admin_token, self::TOKEN_ADMIN);
|
||||
$manager->persist($admin_token);
|
||||
|
||||
$full_token = new ApiToken();
|
||||
$full_token->setUser($admin_user);
|
||||
$full_token->setLevel(ApiTokenLevel::FULL);
|
||||
$full_token->setName('full');
|
||||
$this->setTokenSecret($full_token, self::TOKEN_FULL);
|
||||
$manager->persist($full_token);
|
||||
|
||||
$expired_token = new ApiToken();
|
||||
$expired_token->setUser($admin_user);
|
||||
$expired_token->setLevel(ApiTokenLevel::FULL);
|
||||
$expired_token->setName('expired');
|
||||
$expired_token->setValidUntil(new \DateTimeImmutable('-1 day'));
|
||||
$this->setTokenSecret($expired_token, self::TOKEN_EXPIRED);
|
||||
$manager->persist($expired_token);
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
private function setTokenSecret(ApiToken $token, string $secret): void
|
||||
{
|
||||
//Access private property
|
||||
$reflection = new \ReflectionClass($token);
|
||||
$property = $reflection->getProperty('token');
|
||||
$property->setValue($token, $secret);
|
||||
}
|
||||
|
||||
public function getDependencies(): array
|
||||
{
|
||||
return [UserFixtures::class];
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\UserSystem\User;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
@@ -51,7 +51,7 @@ class DataStructureFixtures extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
//Reset autoincrement
|
||||
$types = [AttachmentType::class, Project::class, Category::class, Footprint::class, Manufacturer::class,
|
||||
MeasurementUnit::class, Storelocation::class, Supplier::class,];
|
||||
MeasurementUnit::class, StorageLocation::class, Supplier::class,];
|
||||
|
||||
foreach ($types as $type) {
|
||||
$this->createNodesForClass($type, $manager);
|
||||
|
||||
@@ -49,7 +49,7 @@ use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Entity\Parts\PartLot;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\PriceInformations\Orderdetail;
|
||||
use App\Entity\PriceInformations\Pricedetail;
|
||||
@@ -83,6 +83,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
|
||||
$part->setManufacturer($manager->find(Manufacturer::class, 1));
|
||||
$part->setTags('test, Test, Part2');
|
||||
$part->setMass(100.2);
|
||||
$part->setIpn('IPN123');
|
||||
$part->setNeedsReview(true);
|
||||
$part->setManufacturingStatus(ManufacturingStatus::ACTIVE);
|
||||
$manager->persist($part);
|
||||
@@ -94,14 +95,15 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
|
||||
$part->setCategory($manager->find(Category::class, 1));
|
||||
$partLot1 = new PartLot();
|
||||
$partLot1->setAmount(1.0);
|
||||
$partLot1->setStorageLocation($manager->find(Storelocation::class, 1));
|
||||
$partLot1->setStorageLocation($manager->find(StorageLocation::class, 1));
|
||||
$part->addPartLot($partLot1);
|
||||
|
||||
$partLot2 = new PartLot();
|
||||
$partLot2->setExpirationDate(new DateTime());
|
||||
$partLot2->setComment('Test');
|
||||
$partLot2->setNeedsRefill(true);
|
||||
$partLot2->setStorageLocation($manager->find(Storelocation::class, 3));
|
||||
$partLot2->setStorageLocation($manager->find(StorageLocation::class, 3));
|
||||
$partLot2->setVendorBarcode('lot2_vendor_barcode');
|
||||
$part->addPartLot($partLot2);
|
||||
|
||||
$orderdetail = new Orderdetail();
|
||||
|
||||
@@ -31,6 +31,8 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
class UserFixtures extends Fixture implements DependentFixtureInterface
|
||||
{
|
||||
public const ADMIN = 'user-admin';
|
||||
|
||||
public function __construct(protected UserPasswordHasherInterface $encoder, protected EntityManagerInterface $em)
|
||||
{
|
||||
}
|
||||
@@ -50,6 +52,7 @@ class UserFixtures extends Fixture implements DependentFixtureInterface
|
||||
$admin->setNeedPwChange(false);
|
||||
$admin->setGroup($this->getReference(GroupFixtures::ADMINS));
|
||||
$manager->persist($admin);
|
||||
$this->addReference(self::ADMIN, $admin);
|
||||
|
||||
$user = new User();
|
||||
$user->setName('user');
|
||||
|
||||
@@ -49,7 +49,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
* This way we save the overhead of the fetch join query for the count and counting, which can be very slow, cause
|
||||
* no indexes can be used.
|
||||
*/
|
||||
class TwoStepORMAdapater extends ORMAdapter
|
||||
class TwoStepORMAdapter extends ORMAdapter
|
||||
{
|
||||
private \Closure $detailQueryCallable;
|
||||
|
||||
@@ -37,7 +37,7 @@ use App\Entity\Parts\Category;
|
||||
use App\Entity\Parts\Footprint;
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\MeasurementUnit;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\Parts\Supplier;
|
||||
use App\Entity\UserSystem\User;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
@@ -118,7 +118,7 @@ class PartFilter implements FilterInterface
|
||||
$this->lotCount = new IntConstraint('COUNT(partLots)');
|
||||
$this->lessThanDesired = new LessThanDesiredConstraint();
|
||||
|
||||
$this->storelocation = new EntityConstraint($nodesListBuilder, Storelocation::class, 'partLots.storage_location');
|
||||
$this->storelocation = new EntityConstraint($nodesListBuilder, StorageLocation::class, 'partLots.storage_location');
|
||||
$this->lotNeedsRefill = new BooleanConstraint('partLots.needs_refill');
|
||||
$this->lotUnknownAmount = new BooleanConstraint('partLots.instock_unknown');
|
||||
$this->lotExpirationDate = new DateTimeConstraint('partLots.expiration_date');
|
||||
|
||||
130
src/DataTables/Helpers/ColumnSortHelper.php
Normal file
130
src/DataTables/Helpers/ColumnSortHelper.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\DataTables\Helpers;
|
||||
|
||||
use Omines\DataTablesBundle\DataTable;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class ColumnSortHelper
|
||||
{
|
||||
private array $columns = [];
|
||||
|
||||
public function __construct(private readonly LoggerInterface $logger)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new column which can be sorted and visibility controlled by the user. The basic syntax is similar to
|
||||
* the DataTable add method, but with additional options.
|
||||
* @param string $name
|
||||
* @param string $type
|
||||
* @param array $options
|
||||
* @param string|null $alias If an alias is set here, the column will be available under this alias in the config
|
||||
* string instead of the name.
|
||||
* @param bool $visibility_configurable If set to false, this column can not be visibility controlled by the user
|
||||
* @return $this
|
||||
*/
|
||||
public function add(string $name, string $type, array $options = [], ?string $alias = null,
|
||||
bool $visibility_configurable = true): self
|
||||
{
|
||||
//Alias allows us to override the name of the column in the env variable
|
||||
$this->columns[$alias ?? $name] = [
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'options' => $options,
|
||||
'visibility_configurable' => $visibility_configurable
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all columns saved inside this helper
|
||||
* @return void
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
$this->columns = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the visibility configuration to the given DataTable and configure the columns.
|
||||
* @param DataTable $dataTable
|
||||
* @param string|array $visible_columns Either a list or a comma separated string of column names, which should
|
||||
* be visible by default. If a column is not listed here, it will be hidden by default.
|
||||
* @return void
|
||||
*/
|
||||
public function applyVisibilityAndConfigureColumns(DataTable $dataTable, string|array $visible_columns,
|
||||
string $config_var_name): void
|
||||
{
|
||||
//If the config is given as a string, convert it to an array first
|
||||
if (!is_array($visible_columns)) {
|
||||
$visible_columns = array_map(trim(...), explode(",", $visible_columns));
|
||||
}
|
||||
|
||||
$processed_columns = [];
|
||||
|
||||
//First add all columns which visibility is not configurable
|
||||
foreach ($this->columns as $col_id => $col_data) {
|
||||
if (!$col_data['visibility_configurable']) {
|
||||
$this->addColumnEntry($dataTable, $this->columns[$col_id], null);
|
||||
$processed_columns[] = $col_id;
|
||||
}
|
||||
}
|
||||
|
||||
//Afterwards the columns, which should be visible by default
|
||||
foreach ($visible_columns as $col_id) {
|
||||
if (!isset($this->columns[$col_id]) || !$this->columns[$col_id]['visibility_configurable']) {
|
||||
$this->logger->warning("Configuration option $config_var_name specify invalid column '$col_id'. Column is skipped.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($col_id, $processed_columns, true)) {
|
||||
$this->logger->warning("Configuration option $config_var_name specify column '$col_id' multiple time. Only first occurrence is used.");
|
||||
continue;
|
||||
}
|
||||
$this->addColumnEntry($dataTable, $this->columns[$col_id], true);
|
||||
$processed_columns[] = $col_id;
|
||||
}
|
||||
|
||||
//and the remaining non-visible columns
|
||||
foreach ($this->columns as $col_id => $col_data) {
|
||||
if (in_array($col_id, $processed_columns, true)) {
|
||||
// column already processed
|
||||
continue;
|
||||
}
|
||||
$this->addColumnEntry($dataTable, $this->columns[$col_id], false);
|
||||
$processed_columns[] = $col_id;
|
||||
}
|
||||
}
|
||||
|
||||
private function addColumnEntry(DataTable $dataTable, array $entry, ?bool $visible): void
|
||||
{
|
||||
$options = $entry['options'] ?? [];
|
||||
if (!is_null($visible)) {
|
||||
$options["visible"] = $visible;
|
||||
}
|
||||
$dataTable->add($entry['name'], $entry['type'], $options);
|
||||
}
|
||||
}
|
||||
@@ -20,14 +20,17 @@ declare(strict_types=1);
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\DataTables\Helpers;
|
||||
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Services\Attachments\AttachmentURLGenerator;
|
||||
use App\Services\Attachments\PartPreviewGenerator;
|
||||
use App\Services\EntityURLGenerator;
|
||||
use App\Services\Formatters\AmountFormatter;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
@@ -35,8 +38,13 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
*/
|
||||
class PartDataTableHelper
|
||||
{
|
||||
public function __construct(private readonly PartPreviewGenerator $previewGenerator, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly EntityURLGenerator $entityURLGenerator, private readonly TranslatorInterface $translator)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PartPreviewGenerator $previewGenerator,
|
||||
private readonly AttachmentURLGenerator $attachmentURLGenerator,
|
||||
private readonly EntityURLGenerator $entityURLGenerator,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly AmountFormatter $amountFormatter,
|
||||
) {
|
||||
}
|
||||
|
||||
public function renderName(Part $context): string
|
||||
@@ -45,14 +53,16 @@ class PartDataTableHelper
|
||||
|
||||
//Depending on the part status we show a different icon (the later conditions have higher priority)
|
||||
if ($context->isFavorite()) {
|
||||
$icon = sprintf('<i class="fa-solid fa-star fa-fw me-1" title="%s"></i>', $this->translator->trans('part.favorite.badge'));
|
||||
$icon = sprintf('<i class="fa-solid fa-star fa-fw me-1" title="%s"></i>',
|
||||
$this->translator->trans('part.favorite.badge'));
|
||||
}
|
||||
if ($context->isNeedsReview()) {
|
||||
$icon = sprintf('<i class="fa-solid fa-ambulance fa-fw me-1" title="%s"></i>', $this->translator->trans('part.needs_review.badge'));
|
||||
$icon = sprintf('<i class="fa-solid fa-ambulance fa-fw me-1" title="%s"></i>',
|
||||
$this->translator->trans('part.needs_review.badge'));
|
||||
}
|
||||
if ($context->getBuiltProject() instanceof Project) {
|
||||
$icon = sprintf('<i class="fa-solid fa-box-archive fa-fw me-1" title="%s"></i>',
|
||||
$this->translator->trans('part.info.projectBuildPart.hint') . ': ' . $context->getBuiltProject()->getName());
|
||||
$this->translator->trans('part.info.projectBuildPart.hint').': '.$context->getBuiltProject()->getName());
|
||||
}
|
||||
|
||||
|
||||
@@ -85,4 +95,62 @@ class PartDataTableHelper
|
||||
$title
|
||||
);
|
||||
}
|
||||
|
||||
public function renderStorageLocations(Part $context): string
|
||||
{
|
||||
$tmp = [];
|
||||
foreach ($context->getPartLots() as $lot) {
|
||||
//Ignore lots without storelocation
|
||||
if (!$lot->getStorageLocation() instanceof StorageLocation) {
|
||||
continue;
|
||||
}
|
||||
$tmp[] = sprintf(
|
||||
'<a href="%s" title="%s">%s</a>',
|
||||
$this->entityURLGenerator->listPartsURL($lot->getStorageLocation()),
|
||||
htmlspecialchars($lot->getStorageLocation()->getFullPath()),
|
||||
htmlspecialchars($lot->getStorageLocation()->getName())
|
||||
);
|
||||
}
|
||||
|
||||
return implode('<br>', $tmp);
|
||||
}
|
||||
|
||||
public function renderAmount(Part $context): string
|
||||
{
|
||||
$amount = $context->getAmountSum();
|
||||
$expiredAmount = $context->getExpiredAmountSum();
|
||||
|
||||
$ret = '';
|
||||
|
||||
if ($context->isAmountUnknown()) {
|
||||
//When all amounts are unknown, we show a question mark
|
||||
if ($amount === 0.0) {
|
||||
$ret .= sprintf('<b class="text-primary" title="%s">?</b>',
|
||||
$this->translator->trans('part_lots.instock_unknown'));
|
||||
} else { //Otherwise mark it with greater equal and the (known) amount
|
||||
$ret .= sprintf('<b class="text-primary" title="%s">≥</b>',
|
||||
$this->translator->trans('part_lots.instock_unknown')
|
||||
);
|
||||
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
|
||||
}
|
||||
} else {
|
||||
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
|
||||
}
|
||||
|
||||
//If we have expired lots, we show them in parentheses behind
|
||||
if ($expiredAmount > 0) {
|
||||
$ret .= sprintf(' <span title="%s" class="text-muted">(+%s)</span>',
|
||||
$this->translator->trans('part_lots.is_expired'),
|
||||
htmlspecialchars($this->amountFormatter->format($expiredAmount, $context->getPartUnit())));
|
||||
}
|
||||
|
||||
//When the amount is below the minimum amount, we highlight the number red
|
||||
if ($context->isNotEnoughInstock()) {
|
||||
$ret = sprintf('<b class="text-danger" title="%s">%s</b>',
|
||||
$this->translator->trans('part.info.amount.less_than_desired'),
|
||||
$ret);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ class LogDataTable implements DataTableTypeInterface
|
||||
if ($context instanceof PartStockChangedLogEntry) {
|
||||
$text .= sprintf(
|
||||
' (<i>%s</i>)',
|
||||
$this->translator->trans('log.part_stock_changed.' . $context->getInstockChangeType()->toExtraShortType())
|
||||
$this->translator->trans($context->getInstockChangeType()->toTranslationKey())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,9 @@ declare(strict_types=1);
|
||||
namespace App\DataTables;
|
||||
|
||||
use App\DataTables\Adapters\FetchResultsAtOnceORMAdapter;
|
||||
use App\DataTables\Adapters\TwoStepORMAdapater;
|
||||
use App\DataTables\Adapters\TwoStepORMAdapter;
|
||||
use App\DataTables\Column\EnumColumn;
|
||||
use App\DataTables\Helpers\ColumnSortHelper;
|
||||
use App\Doctrine\Helpers\FieldHelper;
|
||||
use App\Entity\Parts\ManufacturingStatus;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -34,7 +35,7 @@ use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\Event\ORMAdapterQueryEvent;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapterEvents;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Entity\Parts\StorageLocation;
|
||||
use App\DataTables\Column\EntityColumn;
|
||||
use App\DataTables\Column\IconLinkColumn;
|
||||
use App\DataTables\Column\LocaleDateTimeColumn;
|
||||
@@ -62,8 +63,15 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class PartsDataTable implements DataTableTypeInterface
|
||||
{
|
||||
public function __construct(private readonly EntityURLGenerator $urlGenerator, private readonly TranslatorInterface $translator, private readonly AmountFormatter $amountFormatter, private readonly PartDataTableHelper $partDataTableHelper, private readonly Security $security)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityURLGenerator $urlGenerator,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly AmountFormatter $amountFormatter,
|
||||
private readonly PartDataTableHelper $partDataTableHelper,
|
||||
private readonly Security $security,
|
||||
private readonly string $visible_columns,
|
||||
private readonly ColumnSortHelper $csh,
|
||||
) {
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $optionsResolver): void
|
||||
@@ -83,9 +91,9 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
$this->configureOptions($resolver);
|
||||
$options = $resolver->resolve($options);
|
||||
|
||||
$dataTable
|
||||
$this->csh
|
||||
//Color the table rows depending on the review and favorite status
|
||||
->add('dont_matter', RowClassColumn::class, [
|
||||
->add('row_color', RowClassColumn::class, [
|
||||
'render' => function ($value, Part $context): string {
|
||||
if ($context->isNeedsReview()) {
|
||||
return 'table-secondary';
|
||||
@@ -96,183 +104,106 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
|
||||
return ''; //Default coloring otherwise
|
||||
},
|
||||
])
|
||||
|
||||
->add('select', SelectColumn::class)
|
||||
], visibility_configurable: false)
|
||||
->add('select', SelectColumn::class, visibility_configurable: false)
|
||||
->add('picture', TextColumn::class, [
|
||||
'label' => '',
|
||||
'className' => 'no-colvis',
|
||||
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderPicture($context),
|
||||
])
|
||||
], visibility_configurable: false)
|
||||
->add('name', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.name'),
|
||||
'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context),
|
||||
])
|
||||
->add('id', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.id'),
|
||||
'visible' => false,
|
||||
])
|
||||
->add('ipn', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.ipn'),
|
||||
'visible' => false,
|
||||
])
|
||||
->add('description', MarkdownColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.description'),
|
||||
]);
|
||||
|
||||
if ($this->security->isGranted('@categories.read')) {
|
||||
$dataTable->add('category', EntityColumn::class, [
|
||||
])
|
||||
->add('category', EntityColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.category'),
|
||||
'property' => 'category',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->security->isGranted('@footprints.read')) {
|
||||
$dataTable->add('footprint', EntityColumn::class, [
|
||||
])
|
||||
->add('footprint', EntityColumn::class, [
|
||||
'property' => 'footprint',
|
||||
'label' => $this->translator->trans('part.table.footprint'),
|
||||
]);
|
||||
}
|
||||
if ($this->security->isGranted('@manufacturers.read')) {
|
||||
$dataTable->add('manufacturer', EntityColumn::class, [
|
||||
])
|
||||
->add('manufacturer', EntityColumn::class, [
|
||||
'property' => 'manufacturer',
|
||||
'label' => $this->translator->trans('part.table.manufacturer'),
|
||||
]);
|
||||
}
|
||||
if ($this->security->isGranted('@storelocations.read')) {
|
||||
$dataTable->add('storelocation', TextColumn::class, [
|
||||
])
|
||||
->add('storelocation', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.storeLocations'),
|
||||
'orderField' => 'storelocations.name',
|
||||
'render' => function ($value, Part $context): string {
|
||||
$tmp = [];
|
||||
foreach ($context->getPartLots() as $lot) {
|
||||
//Ignore lots without storelocation
|
||||
if (!$lot->getStorageLocation() instanceof Storelocation) {
|
||||
continue;
|
||||
}
|
||||
$tmp[] = sprintf(
|
||||
'<a href="%s" title="%s">%s</a>',
|
||||
$this->urlGenerator->listPartsURL($lot->getStorageLocation()),
|
||||
htmlspecialchars($lot->getStorageLocation()->getFullPath()),
|
||||
htmlspecialchars($lot->getStorageLocation()->getName())
|
||||
);
|
||||
}
|
||||
|
||||
return implode('<br>', $tmp);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
$dataTable->add('amount', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.amount'),
|
||||
'render' => function ($value, Part $context) {
|
||||
$amount = $context->getAmountSum();
|
||||
$expiredAmount = $context->getExpiredAmountSum();
|
||||
|
||||
$ret = '';
|
||||
|
||||
if ($context->isAmountUnknown()) {
|
||||
//When all amounts are unknown, we show a question mark
|
||||
if ($amount === 0.0) {
|
||||
$ret .= sprintf('<b class="text-primary" title="%s">?</b>',
|
||||
$this->translator->trans('part_lots.instock_unknown'));
|
||||
} else { //Otherwise mark it with greater equal and the (known) amount
|
||||
$ret .= sprintf('<b class="text-primary" title="%s">≥</b>',
|
||||
$this->translator->trans('part_lots.instock_unknown')
|
||||
);
|
||||
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
|
||||
}
|
||||
} else {
|
||||
$ret .= htmlspecialchars($this->amountFormatter->format($amount, $context->getPartUnit()));
|
||||
}
|
||||
|
||||
//If we have expired lots, we show them in parentheses behind
|
||||
if ($expiredAmount > 0) {
|
||||
$ret .= sprintf(' <span title="%s" class="text-muted">(+%s)</span>',
|
||||
$this->translator->trans('part_lots.is_expired'),
|
||||
htmlspecialchars($this->amountFormatter->format($expiredAmount, $context->getPartUnit())));
|
||||
}
|
||||
|
||||
//When the amount is below the minimum amount, we highlight the number red
|
||||
if ($context->isNotEnoughInstock()) {
|
||||
$ret = sprintf('<b class="text-danger" title="%s">%s</b>',
|
||||
$this->translator->trans('part.info.amount.less_than_desired'),
|
||||
$ret);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
},
|
||||
'orderField' => 'amountSum'
|
||||
])
|
||||
'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderStorageLocations($context),
|
||||
], alias: 'storage_location')
|
||||
->add('amount', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.amount'),
|
||||
'render' => fn ($value, Part $context) => $this->partDataTableHelper->renderAmount($context),
|
||||
'orderField' => 'amountSum'
|
||||
])
|
||||
->add('minamount', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.minamount'),
|
||||
'visible' => false,
|
||||
'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format($value, $context->getPartUnit())),
|
||||
]);
|
||||
|
||||
if ($this->security->isGranted('@footprints.read')) {
|
||||
$dataTable->add('partUnit', TextColumn::class, [
|
||||
'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format($value,
|
||||
$context->getPartUnit())),
|
||||
])
|
||||
->add('partUnit', TextColumn::class, [
|
||||
'field' => 'partUnit.name',
|
||||
'label' => $this->translator->trans('part.table.partUnit'),
|
||||
'visible' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
$dataTable->add('addedDate', LocaleDateTimeColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.addedDate'),
|
||||
'visible' => false,
|
||||
])
|
||||
])
|
||||
->add('addedDate', LocaleDateTimeColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.addedDate'),
|
||||
])
|
||||
->add('lastModified', LocaleDateTimeColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.lastModified'),
|
||||
'visible' => false,
|
||||
])
|
||||
->add('needs_review', PrettyBoolColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.needsReview'),
|
||||
'visible' => false,
|
||||
])
|
||||
->add('favorite', PrettyBoolColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.favorite'),
|
||||
'visible' => false,
|
||||
])
|
||||
->add('manufacturing_status', EnumColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.manufacturingStatus'),
|
||||
'visible' => false,
|
||||
'class' => ManufacturingStatus::class,
|
||||
'render' => function(?ManufacturingStatus $status, Part $context): string {
|
||||
'render' => function (?ManufacturingStatus $status, Part $context): string {
|
||||
if (!$status) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->translator->trans($status->toTranslationKey());
|
||||
} ,
|
||||
},
|
||||
])
|
||||
->add('manufacturer_product_number', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.mpn'),
|
||||
'visible' => false,
|
||||
])
|
||||
->add('mass', SIUnitNumberColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.mass'),
|
||||
'visible' => false,
|
||||
'unit' => 'g'
|
||||
])
|
||||
->add('tags', TagsColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.tags'),
|
||||
'visible' => false,
|
||||
])
|
||||
->add('attachments', PartAttachmentsColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.attachments'),
|
||||
'visible' => false,
|
||||
])
|
||||
->add('edit', IconLinkColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.edit'),
|
||||
'visible' => false,
|
||||
'href' => fn($value, Part $context) => $this->urlGenerator->editURL($context),
|
||||
'disabled' => fn($value, Part $context) => !$this->security->isGranted('edit', $context),
|
||||
'title' => $this->translator->trans('part.table.edit.title'),
|
||||
])
|
||||
]);
|
||||
|
||||
->addOrderBy('name')
|
||||
->createAdapter(TwoStepORMAdapater::class, [
|
||||
//Apply the user configured order and visibility and add the columns to the table
|
||||
$this->csh->applyVisibilityAndConfigureColumns($dataTable, $this->visible_columns,
|
||||
"TABLE_PARTS_DEFAULT_COLUMNS");
|
||||
|
||||
$dataTable->addOrderBy('name')
|
||||
->createAdapter(TwoStepORMAdapter::class, [
|
||||
'filter_query' => $this->getFilterQuery(...),
|
||||
'detail_query' => $this->getDetailQuery(...),
|
||||
'entity' => Part::class,
|
||||
@@ -300,7 +231,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
->addSelect(
|
||||
'(
|
||||
SELECT IFNULL(SUM(partLot.amount), 0.0)
|
||||
FROM '. PartLot::class. ' partLot
|
||||
FROM '.PartLot::class.' partLot
|
||||
WHERE partLot.part = part.id
|
||||
AND partLot.instock_unknown = false
|
||||
AND (partLot.expiration_date IS NULL OR partLot.expiration_date > CURRENT_DATE())
|
||||
@@ -321,8 +252,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
->leftJoin('part.parameters', 'parameters')
|
||||
|
||||
//This must be the only group by, or the paginator will not work correctly
|
||||
->addGroupBy('part.id')
|
||||
;
|
||||
->addGroupBy('part.id');
|
||||
}
|
||||
|
||||
private function getDetailQuery(QueryBuilder $builder, array $filter_results): void
|
||||
@@ -352,7 +282,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
->addSelect(
|
||||
'(
|
||||
SELECT IFNULL(SUM(partLot.amount), 0.0)
|
||||
FROM '. PartLot::class. ' partLot
|
||||
FROM '.PartLot::class.' partLot
|
||||
WHERE partLot.part = part.id
|
||||
AND partLot.instock_unknown = false
|
||||
AND (partLot.expiration_date IS NULL OR partLot.expiration_date > CURRENT_DATE())
|
||||
@@ -371,7 +301,6 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
->leftJoin('part.attachments', 'attachments')
|
||||
->leftJoin('part.partUnit', 'partUnit')
|
||||
->leftJoin('part.parameters', 'parameters')
|
||||
|
||||
->where('part.id IN (:ids)')
|
||||
->setParameter('ids', $ids)
|
||||
|
||||
@@ -388,8 +317,7 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
->addGroupBy('suppliers')
|
||||
->addGroupBy('attachments')
|
||||
->addGroupBy('partUnit')
|
||||
->addGroupBy('parameters')
|
||||
;
|
||||
->addGroupBy('parameters');
|
||||
|
||||
//Get the results in the same order as the IDs were passed
|
||||
FieldHelper::addOrderByFieldParam($builder, 'part.id', 'ids');
|
||||
@@ -408,6 +336,5 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||
$filter = $options['filter'];
|
||||
$filter->apply($builder);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,16 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||
throw new \Exception('This should never happen!');
|
||||
},
|
||||
])
|
||||
|
||||
->add('ipn', TextColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.ipn'),
|
||||
'orderField' => 'part.ipn',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
if($context->getPart() instanceof Part) {
|
||||
return $context->getPart()->getIpn();
|
||||
}
|
||||
}
|
||||
])
|
||||
->add('description', MarkdownColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.description'),
|
||||
'data' => function (ProjectBOMEntry $context) {
|
||||
@@ -142,6 +151,28 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||
},
|
||||
])
|
||||
|
||||
->add('instockAmount', TextColumn::class, [
|
||||
'label' => 'project.bom.instockAmount',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
if ($context->getPart()) {
|
||||
return $this->partDataTableHelper->renderAmount($context->getPart());
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
])
|
||||
->add('storageLocations', TextColumn::class, [
|
||||
'label' => 'part.table.storeLocations',
|
||||
'visible' => false,
|
||||
'render' => function ($value, ProjectBOMEntry $context) {
|
||||
if ($context->getPart()) {
|
||||
return $this->partDataTableHelper->renderStorageLocations($context->getPart());
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
])
|
||||
|
||||
->add('addedDate', LocaleDateTimeColumn::class, [
|
||||
'label' => $this->translator->trans('part.table.addedDate'),
|
||||
|
||||
@@ -36,7 +36,7 @@ class Field2 extends FunctionNode
|
||||
|
||||
private $values = [];
|
||||
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser): void
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
@@ -58,7 +58,7 @@ class Field2 extends FunctionNode
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker): string
|
||||
{
|
||||
$query = 'FIELD2(';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user